What does the letter 'L' stand for in the SOLID principles?
sobes.tech AI
Answer from AI
Liskov Substitution Principle (LSP).
According to this principle, objects in a program should be replaceable with instances of their subtypes without altering the correctness of the program.
In other words, if class B is a subtype of class A, then any object of type A in the program can be replaced with an object of type B, and the program will continue to work correctly.
This principle helps create more flexible and resilient architecture, minimizing side effects when working with inheritance.
Violation of LSP:
class Rectangle {
var width: Double
var height: Double
init(width: Double, height: Double) {
self.width = width
self.height = height
}
var area: Double {
return width * height
}
}
class Square: Rectangle { // Square is a subtype of Rectangle
override var width: Double {
didSet {
height = width // Changing width also changes height
}
}
override var height: Double {
didSet {
width = height // Changing height also changes width
}
}
init(side: Double) {
super.init(width: side, height: side)
}
}
func calculateArea(of rectangle: Rectangle) -> Double {
return rectangle.area
}
let myRectangle = Rectangle(width: 5, height: 10)
print(calculateArea(of: myRectangle)) // 50
let mySquare = Square(side: 5)
// We assume this is a 5x5 rectangle with an area of 25.
// But when used in a function expecting a Rectangle,
// its behavior can be unpredictable if width or height is changed
// before calling calculateArea, since Square changes both sides simultaneously.
// In this example, if properties are not changed after initialization,
// the result will be 25, but if, for example, mySquare.width = 10
// is set before calling calculateArea, its area will become 100, which can
// be unexpected for code working with Rectangle.
print(calculateArea(of: mySquare)) // 25
In this example, Square violates LSP because changing one dimension (width or height) also changes the other. If we use a Square object where a Rectangle is expected, it can lead to unexpected behavior, as code working with Rectangle may assume the ability to change sides independently.
LSP Compliant:
To adhere to LSP in this case, the hierarchy can be split or composition can be used instead of inheritance for Square. For example, introducing a common interface or protocol for shapes that have an area, and implementing it in Rectangle and Square.
protocol Shape {
var area: Double { get }
}
class Rectangle: Shape {
var width: Double
var height: Double
init(width: Double, height: Double) {
self.width = width
self.height = height
}
var area: Double {
return width * height
}
}
class Square: Shape {
var side: Double
init(side: Double) {
self.side = side
}
var area: Double {
return side * side
}
}
func calculateShapeArea(of shape: Shape) -> Double {
return shape.area
}
let myShapeRectangle: Shape = Rectangle(width: 5, height: 10)
print(calculateShapeArea(of: myShapeRectangle)) // 50
let myShapeSquare: Shape = Square(side: 5)
print(calculateShapeArea(of: myShapeSquare)) // 25
Now, both Square and Rectangle implement the common Shape protocol, and we can use objects of both classes where a Shape is expected without violating expected behavior.