[디자인패턴의 아름다움] 리스코프 치환 원칙

리스코프 치환의 원칙

만약 S가 T의 하위 유형인 경우, T 유형의 객체는 프로그램을 중단하지 않고도 S 유형의 객체로 대체될 수 있다. -바바라 리스코프-

기본 클래스에서 참조 포인터를 사용하는 함수는 특별히 인지하지 않고도 파생 클래스의 객체를 사용할 수 있어야 한다. - 로버트 마틴 -

하위 유형 또는 파생 클래스의 객체는 프로그램 내에서 상위 클래스가 나타나는 모든 상황에서 대체 가능하며, 프로그램이 원래 가지는 논리적인 동작이 변경되지 않으며 정확성도 유지된다.

리스코프 치환 원칙과 다형성은 보기에는 비슷하지만, 실제로는 완전히 다른 의미를 담고 있다. 다형성은 코드를 구현하는 방식에 해당하지만, 리스코프 치환 원칙은 상속관계에서 하위 클래스의 설계 방식을 설명하는 설계 원칙에 해당한다. 상위 클래스를 대체할 때 프로그램의 원래 논리적 동작이 변경되지 않고 프로그램의 정확성이 손상되지 않도록 해야한다는 원칙이다.

리스코프 원칙에는 좀 더 이해하기 쉬운 설명 방식이 있는데, 바로 계약에 따른 설계라는 표현이다. 하위 클래스를 설계할 떄는 상위 클래스의 동작 규칙을 따라야 한다. 상위 클래스는 함수의 동작 규칙을 정의하고 하위 클래스는 함수의 내부 구현 논리를 변경할 수 있지만 함수의 원래 동작 규칙은 변경할 수 없다. 동작 규칙에는 함수가 구현하기 위해 선언한 것, 입력, 출력, 예외에 대한 규칙, 주석에 나열된 모든 특수 사례 설명이 포함된다.

하위 클래스의 설계와 구현이 리스코프 치환 원칙을 위반하는지 여부를 판단하기 위한 방법으로 상위 클래스의 단위 테스트를 총해 하위 클래스의 코드르 확인하는 방법도 있다. 만약 일부 단위 테스트가 실행되지 않으면 하위 클래스의 설계와 구현이 상위 클래스의 계약을 완전히 준수 하지 않고 하위 클래스가 리스코프 치환 원칙을 위반할 수 있음을 의미한다.

생각해보기

리스코프 치환 원리의 중요성은 무엇인지 생각해보자.

-> 일관성, 목적? 설계가 구현시 변경되어서는 안된다.

이게 더 읽기 좋을라나…

스터디 내용 발췌

타입이 뭘 뜻하는지 이해한다면 리스코프 치환 규칙에서 제네릭 파라미터 타입의 공변성 반공변성 부분은 그냥 사족일 뿐입니다. 선행조건 강화 금지, 후행조건 완화 금지, 불변조건 지키기, (+구현을 감안한다면 바이너리 레이아웃 호환성) 정도로 충분해요. 예를 들어 다음과 같은 정체불명 K언어의 제네릭 타입이 있다고 칩시다. class GetType<in A, out B> { … fun foo(a:A):B … }

제네릭 타입 파라미터 A가 함수 foo의 인자 타입으로 쓰이는 경우를 생각해보죠. 이 경우 이는 다른 말로 다음과 같이 선언한 것이라고도 볼 수 있어요. fun foo(a):B // precondition(a isa A)

여기서 이 a isa A라는 선행조건을 약화시키는 방향은 A보다 더 느슨한 타입(상위타입)을 제공하는 것이고, 강화시키는 방향은 A보다 더 빡빡한 타입(하위타입)을 제공하는 것이죠. 다른 방법은 없습니다. 왜냐하면 제네릭 타입은 타입 수준에서밖에 이야기를 수 없기 때문에 시그니처만 보고 판단을 해야 하기 때문이죠. 따라서 공유해주신 문서의 앞쪽에서 하위타입이 메서드를 오버라이드하면서 선행조건을 더 강화하지 말라는 논리가 이해 된다면 이 부분에서 같은 논리로 인자의 반 공변성을 설명하는걸 이해할 수 있습니다. 마찬가지로 반환값을 후행조건이라고 생각하면 더 느슨한 후행 조건은 허용하지 않는다는 것으로 생각할 수 있으므로, 상위 타입의 값을 허용하지 못하는 걸로 반환 타입의 공변성을 이해할 수 있습니다. 타입이란 결국 어떤 값에 대해 이 값은 어떤 특성을 만족한다는 명제를 짧게 쓴겁니다. 예를 들어 a:Int는 a 값이 최소 -2^31-1, 최대 +2^31, 크기는 32비트, 적용 가능한 연산들은 뭐뭐 라는 명제를 짧게 적은거죠. 여기에 다른 조건을 더 추가하면 조건이 강화되겠죠. 부모 타입에 조건을 추가해서 더 세분화한게 자식 타입이니까 자식 타입은 부모 타입의 제약을 (부모 타입의 제약과 호환되는 방향에서) 더 강화한 것일 뿐입니다. 여기서 “부모 타입의 제약과 호환”이라는 말에서 “호환”이 무슨 뜻인지를 생각해보다 보니 이걸 리스코프 치환 규칙으로 정의할 수 있는 것이죠. 공변성 반공변성은 거기서 따라나오는 따름정리 정도에 지나지 않습니다. 또 어떤 면에서 보면 리스코프 치환은 그냥 is-a관계를 다른 말로 써놨을 뿐이라고 볼 수도 있을것 같습니다. Child is-a Parent 라는 관계가 성립하려면 어때야 할까요? 결국 그걸 풀어쓰다 보면 리스코프 치환에 준하는 무언가를 언급하지 않을 수는 없죠.

results matching ""

    No results matching ""