밤에 쓴 코드

OOP ) LSP - 리스코프 치환 원칙 (Liskov substitution principle) 본문

OOP

OOP ) LSP - 리스코프 치환 원칙 (Liskov substitution principle)

붱이🦉 2019. 5. 18. 21:26

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.시간이흐르다() 를 올바르게 수행하고 , 그후에 상속에서 추가된 프로퍼티에 대해서만 처리를 했기때문에 ,

예상하지 못한 동작을 하지 않는다.

LSP를 지키는 방법은 재정의를 할때는 정말 신중히 고민을 하고 , 올바른 상속을 하면 지킬 수 있다 고 생각한다.

Comments