부모(super class)로 동작하는 곳에서 자식(sub class)를 넣어주어도 대체가 가능해야 한다는 원칙.
자식 클래스를 구현할 때, 기본적으로 부모클래스의 기능이나 능력들을 물려받는다. 여기서 자식클래스는 동작을 할 때, 부모 클래스의 기능들을 제한하면 안된다는 뜻.
부모클래스의 타입에 자식클래스의 인스턴스를 넣어도 똑같이 동작하여야 한다.
나쁜 예.
class Rectangle {
var width: Float = 0
var height: Float = 0
var area: Float{
return width * height
}
}
class Square: Rectangle {
override var width: Float{
didSet{
height = width
}
}
}
func printArea(of rectangle: Rectangle){
rectangle.height = 3
rectangle.width = 6
print(rectangle.area)
}
let rectangle = Rectangle()
printArea(of: rectangle)
let square = Square()
printArea(of: square)
실제로 정사각형은 직사각형이라고 할 수 있다. 이 원리에 따라 프로그램을 다음과 같이 설계하게 되면 문제가 생기게 된다.
바로 정사각형의 넓이를 출력해야할 때 height = width라는 구문으로 인해 printArea(_: Rectangle)에서 원하는 결과를 얻지 못하게 된다.
즉 부모(super, Rectangle)의 역할을 자식(sub, square)에서 대신하지 못하고 있는 상황이 발생하게 된다.
좋은 예
protocol Shape {
var area: Float { get }
}
class Rectangle: Shape{
let width: Float
let height: Float
var area: Float{
return width * height
}
init(width: Float,
height: Float){
self.width = width
self.height = height
}
}
class Square: Shape{
let length: Float
var area: Float{
return length * length
}
init(length: Float) {
self.length = length
}
}
이런 방법으로 Rectangle, Square모두 Shape이라는 protocol을 채택할 수 있게 설계하고 실제 구현부는 채택하는 하위 클래스로 넘기면 LSP의 원칙에 어긋나지 않는 프로그램을 설계할 수 있다.
즉 Shape의 역할을 Square, Rectangle모두가 기존의 룰을 위반하지 않고 동작하는 프로그램이 만들어지게 된다. 이런 상황을 LSP를 지킨 설계라고 하게된다.