LSP - 리스코프 치환 원칙 (Liskov substitution principle)
자식클래스 (서브클래스) 는 부모클래스(슈퍼클래스)의 역할을 완벽히 수행할 수 있어야한다.
호오 당연한 얘기 아닌가?
‘새’를 상속받은 ‘부엉이는 ‘새’로써의 역할을 완벽히 해야한다.
맞는 말인데 이게 좀 처럼 지켜지지 않는다.
이 문제는 서브클래스가 슈퍼클래스의 메소드를 Override 하는 과정에서 발생한다.
class 초시계 {
var 초:Int = 0
func 시간이흐르다(){
초 = 초 == 59 ? 0 : 초 + 1
}
func 시간보여주기(){
print("\(초)초")
}
}
초시계를 만들어 보았다.
근데 초시계
의 동작을 모두 수행하면서 분까지 보고싶은 마음에 , 분초시계
라는 것을 만들었다.
class 잘못된분초시계: 초시계{
var 분:Int = 0
override func 시간이흐르다(){
super.시간이흐르다()
if(초 == 59){
분 += 1
}
}
override func 시간보여주기(){
print("\(분)분")
super.시간보여주기()
}
}
분초시계를 만드는 과정에 , 초시계로서의 역할을 재사용 하기위해서 상속을 받았다.
문제가 없어 보인다. 하지만
let 어떤초시계 : 초시계 = 분초시계()
let 잘못된초시계: 초시계 = 잘못된분초시계()
for _ in 0...65{
어떤초시계.시간보여주기() // *분*초
어떤초시계.시간이흐르다()
}
??? 분명히 초시계인데 분까지 보여준다 . 나는 분명히 초시계를 사용하기 위해서 사용했지만 이상하게도 이 오브젝트는 엉뚱한 동작을 한다.
분초시계는 초시계로서의 역할을 하면서 추가적인 확정적인 동작을 하는 것이지 , 기존 초시계 의 동작을 잃어버려서는 안된다.
만약 잃어버리게 해야한다면 , 이것은 상속이 올바른 구조인지 한번 더 생각해 보아야한다.
class 분초시계: 초시계{
var 분:Int = 0
override func 시간이흐르다(){
super.시간이흐르다()
if(초 == 59){
분 += 1
}
}
func 분보여주기(){
print("\(분)분")
}
func 분초보여주기(){
self.분보여주기()
super.시간보여주기()
}
}
이렇게 추가적인 확장이 올바른 것이다.
LSP를 지키는 가장 간단한 방법은 Override를 안하는 방법이다.
맞는 말이지만 , 가장 간단한 방법이지 , 무조건 적인건 아니다.
위의 예에서 , 시간이흐르다() 라는 메소드는 Override 를 했음에도 불구하고, 올바르게 부모클래스로서의 역할을 수행하고있다.
부모클래스의 메소드 super.시간이흐르다() 를 올바르게 수행하고 , 그후에 상속에서 추가된 프로퍼티에 대해서만 처리를 했기때문에 ,
예상하지 못한 동작을 하지 않는다.