13장에서는 LSP가 가장 기억에 남습니다. 사실 이번 장을 공부하기 이전에는 LSP에 대해 알고 있을뿐 그 안에 숨어있는 내용까지는 알지 못하였습니다. 그러나 이번 장에서 타입, 계층, LSP에 대해 언급을 하고 그를 기반으로 LSP에 대해 다시 이야기를 해주었을때 부모클래스와 자식 클래스의 자리를 서로 바꾸어도 프로그램이 동작해야 한다 라는 말의 중요성을 깨닫게 되었습니다.
상속의 첫 번째 용도는 타입 계층을 구현하는 것이다.
...
상속의 두 번째 용도는 코드 재사용이다.
...
상송의 가치는 이러한 타입 계층을 구현할 수 있는 편안한 방법을 제공한다는 데 있다.
이전 장에서부터 반복해서 이야기한 상속의 용도에 대해 말하고 있습니다. 그리고 13장에서는 타입계층을 목적으로 상속을 사용할 때는 언제고 코드 재사용을 목적으로 상속을 사용할 때는 언제인지 말하고 있습니다
01 타입
개념 관점의 타입
개념 관점에서 타입이란 우리가 인지하는 세상의 사물의 종류를 의미한다.
...
어떤 대상이 타입으로 분류될때 그 대상을 타입의 인스턴스라고 부른다.
...
일반적으로 타입의 인스턴스를 객체라고 부른다.
그 동안 사용하였던 타입의 정의에 대해 이야기 하고 있습니다. 그리고 이 이야기는 추후에도 계속해 기술할 것이므로 길게 설명하고 있지는 않습니다. 그리고 객체와 인스턴스의 차이점에 대해 이야기하고 있습니다.
프로그래밍 언어 관점의 타입
프로그래밍 언어에서 타입은 두 가지 목적을 위해 사용된다.
1. 타입에 수행될 수 있는 유효한 오퍼레이션의 집합을 정의한다.
2. 타입에 수행되는 오퍼레이션에 대해 미리 약속된 문맥을 제공한다.
타입에 대해 기술적으로 정의한 글입니다. 특히 오퍼레이션의 집합으로 정의를 내린것이 흥미로웠으며 타입이란 정수형 혹은 실수형 혹은 문자열 과 같은 것만 생각하였던 저의 생각과 달리 다른 의미로 접근을 하였기에 더 기억에 남습니다.
객체지향 패러다임 관점의 타입
따라서 객체의 타입이란 객체가 수신할 수 있는 메시지의 종류를 정의하는 것이다.
...
우리는 이미 객체가 수신할 수 있는 메시지의 집합을가리키는 멋진 용어를 알고 있다. 바로 퍼블릭 인터페이스가 그것이다.
...
객체의 퍼블릭 인터페이스가 객체의 타입을 결정한다. 따라서 동일한 퍼블릭 인터페이스를 제공하는 객체들은 동일한 타입으로 분류된다.
타입과 퍼블릭 인터페이스와의 연결점에 대해 이야기하고 있습니다. 즉 고유의 퍼블릭 인터페이스는 하나의 고유한 타입이라는 말입니다. 그리고 이는 추후 나올 다형성과 연관지어도 충분히 일리가 있는 말이라 생각합니다.
02 타입 계층
타입사이의 포함관계
타입 사이에 개념적으로 일반화와 특수화 관계가 존재한다는 것을 의미한다.
...
타입 계층을 구성하는 두 타입 간의 관계에서 더 일반적인 타입을 슈퍼타입이라고 부르고 더 특수한 타입을 서브타입이라고 부른다.
...
일반화는 다른 타입을 완전히 포함하거나 내포하는 타입을 식별하는 행위 또는 그 행위의 결과를 가리킨다. 특수화는 다른 타입 안에 전체적으로 포함되거나 완전히 내포되는 타입을 식별하는 행위 또는 그 행위의 결과를 가리킨다.
타입간 일반화와 특수화에 대해 이야기를 하고 있습니다. 그리고 조금 더 넓은 범위를 가리키는 것은 슈퍼타입 그리고 좁은 범위를 가리키는 것은 서브타입이라 생각합니다.
03 서브클래싱과 서브타이핑
언제 상속을 사용해야 하는가?
다음과 같은 질문을 해보고 두 질문에 모두 '예'라고 답할 수 있는 경우에만 상속을 사용하라고 조언한다.
- 상속 관계가 is-a 관계를 모델링하는가?
- 클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가?
상속을 사용할 때의 볼 수 있는 기준들이라고 생각을 합니다. 저는 그동안 퍼블릭 인터페이스를 만들어두고 이를 이용하는 경우에만 상속을 이용해 구현을 하였습니다. 그러나 이러한 기준들로 인해 명확한 기준을 통해 상속을 할 수 있을듯 합니다.
is-a 관계
어떤 두 대상을 언어적으로 is-a라고 표현할 수 있더라도 일단은 상속을 사용할 예비 후보 정도로만 생각하라.
'언제 상속을 사용해야 하는가?'의 첫번째 질문의 맹점입니다. 자연어만 생각해보았을때는 'is-a'관계가 정의될수 있더라도 객체지향 세계에서는 그렇지 않을 수 있기 때문에 '사용할수도 있다' 정도로만 생각을 하고자 합니다. (ex. 새-펭귄의 관계처럼 자연어에서 바라보았을 때에는 is-a 관계가 맞을 수 있지만 객체지향 세계에서는 꼭 그렇지도 않다는 것입니다. 새 라는 클래스에 fly라는 메서드가 있고 펭귄 클래스가 새 클래스를 상속 받을 경우 펭귄 역시 날 수 있다 라는 결과가 나오기 때문입니다.)
행동 호환성
결론은 두 타입 사이에 행동이 호환될 경우에만 타입 계층으로 묶어야 한다는 것이다.
...
여기서 중요한 것은 행동의 호환 여부를 판단하는 기준은 클라이언트의 관점이라는 것이다. 클라이언트가 두 타입이 동일하게 행동할 것이라고 기대한다면 두 타입을 타입 계층으로 묶을 수 있다.
자연어적인 측면으로 생각을 하지 말고 '행동'을 중심으로 이야기 생각을 해보라는 이야기 입니다. 그리고 만약 행동이 같은 타입이 있다면 하나의 타입으로 묶을수 있다는 것입니다.(같은 행동을 한다고 해서 무조건 같은 타입은 아닙니다.)
클라이언트의 기대에 따라 계층 분리하기
클라이언트에 따라 인터페이스를 분리하면 변경에 대한 영향을 더 세밀하게 제어할 수 있게 된다.
...
이처럼 인터페이스를 클라이언트의 기대에 따라 분리함으로써 변경에 의해 영향을 제어하는 셜계 원칙을 인터페이스 분리 원칙(Interface Segregation Principle, ISP)이라고 부른다.
이전에는 인터페이스 분리원칙을 그저 말 그대로 인터페이스를 분리하는 것 정도로만 생각을 하였습니다. 그러나 타입에 대해 생각을 한 후 ISP에 대해 다시 생각을 해보니 ISP는 클라이언트 관점에서 의미있는 것들끼리 인터페이스를 나누는 것일 수도 있겠다는 생각을 하였습니다.
서브클래싱과 서브타이핑
사람들은 상속을 사용하는 두 가지 목적에 특별한 이름을 붙였는데 서브클래싱과 서브타이핑이 그것이다.
- 서브클래싱: 다른 클래스의 코드를 재사용할 목적으로 상속을 사용하는 경우를 가리킨다.
- 서브타이핑: 타입 계층을 구성하기 위해 상속을 사용하는 경우를 가리킨다.
...
어떤 타입이 다른 타입의 서브타입이 되기 위해서는 행동 호환성을 만족시켜야 한다.
...
자식 클래스와 부모 클래스 사이의 행동 호환성은 부모 클래스에 대한 자식 클래스의 대체 가능성을 포함한다.
상속을 사용하는 목적을 구체적인 이름으로 분류하였습니다. 서브클래싱과 서브타이핑으로 서브클래싱은 이름에도 있듯 class를 강조하기에 코드의 재사용에 목적을 두었습니다. 그리고 서브타이핑은 type을 강조하기에 타입 계층을 구성하는데 목적을 둡니다. 그리고 당연한 이야기이지만 서브타입과 슈퍼타입간 행동호환성 즉 슈퍼타입에서 할 수 있는 행동은 서브타입에서도 전부 할 수 있어야 한다는 것이 행동 호환성입니다.
04 리스코프 치환 원칙
서브타입은 그것의 기반 타입에 대해 대체 가능해야 한다.
추상화를 적용한 코드에 대해 생각을 해보면 당연한 이야기 입니다. 부모 클래스(슈퍼 타입)으로 캐스팅이 되었어도, 자식 클래스(서브 타입)으로 캐스팅이 되었어도 문제없이 동작해야 한다는 이야기 입니다.
클라이언트와 대체 가능성
리스코프 치환 원칙은 "클라이언트와 격리한 채로 본 모델은 의미있게 검증하는 것이 불가능하다"는 아주 중요한 결론을 이끈다.
LSP는 슈퍼 타입이 있는 자리에 서브타입이 존재해도 올바르게 동작해야 함을 의미합니다. 그리고 타입을 결정하는 것을 지극히 주관적인 클라이언트의 시선이므로 LSP와 클라이언트는 뗄래야 뗄수 없는 관계라 생각합니다.
is-a관계 다시 살펴보기
슈퍼타입과 서브타입이 클라이언트 입장에서 행동이 호환된다면 두 타입을 is-a로 연결해 문장을 만들어도 어색하지 않은 단어로 타입의 이름을 정하라는 것이다.
이 부분은 LSP, 타입들 보다 이름을 짓는 하나의 규칙이라 생각이 들었습니다. 그리고 명쾌한 이름을 짓는 것은 코드를 작성하는데 매우 중요한 작업이므로 따로 언급을 하였습니다.
리스코프 치환 원칙은 유연한 설계의 기반이다.
클라이언트의 입장에서 퍼블릭 인터페이스의 행동방식이 변경되지 않는다면 클라이언트의 코드를 변경하지 않고도 새로운 자식 클래스와 협력할 수 있게 된다는 것이다.
LSP의 핵심입니다. 그리고 인터페이스가 변경되지 않는다면 부모 클래스의 객체를 자식 클래스의 객체로 변경해도 괜찮습니다. 그리고 이것은 유연한 설계의 결과라고 생각하기 LSP는 유연한 설계의 기반이라는 말이 나왔다 생각합니다.
05 계약에 의한 설계와 서브 타이핑
서브타입이 리스코프 치환 원치을 만족시키기 위해서는 클라이언트와 슈퍼타입간에 체결된 '계약'을 준수해야 한다.
...
계약에 의한 설계에 따르면 협력하는 클라이언트와 슈퍼타입의 인스턴스 사이에는 어떤 계약이 맺어져 있다. 클라이언트와 슈퍼타입은 이 계약을 준수할 때만 정상적으로 협력할 수 있다.
...
리스코프 치환 원칙은 서브타입이 그것의 슈퍼타입을 대체할 수 있어야 하고 클라이언트가 차이점을 인식하지 못한 채로 슈퍼타입의 인터페이스를 이용해 서브타입과 협력할 수 있어야 한다고 말한다.
저는 인터페이스를 전문적인 용어로 풀어쓴 것이 계약이라 생각합니다. 그리고 계약을 행동호환성 혹은 인터페이스로 변경해 이야기를 하면 위에서 이야기한 것과 유사하다 생각하기에 넘어가겠습니다.
서브타입과 계약
자식 클래스가 부모 클래스의 서브타입이 되기 위해서는 다음 조건을 만족시켜야 한다.
1. 서브타입에 더 강력한 사전조건을 정의할 수 없다.
2. 서브타입에 슈퍼타입과 같거나 더 약한 사전조건을 정의할 수 있다.
3. 서브타입에 슈퍼타입과 같거나 더 강한 사후조건을 정의할 수 있다.
4. 서브타입에 더 약한 사후 조건을 정의할 수 없다.
사전조건은 자식 클래스가 부모클래스보다 같거나 약해야 하며 사후 조건은 자식 클래스가 부모 클래스보다 같거나 강해야 한다는 말입니다. 사전조건은 ,개인적인 생각으로, 메서드의 인자정도가 있을 것 같습니다 그리고 사후조건으로는 자식 클래스가 의미하는 것이 무엇인지에 따라 조건을 강하게 걸면 되리라 생각합니다.
'책 > 오브젝트 (완)' 카테고리의 다른 글
15 디자인 패턴과 프레임워크 (0) | 2024.02.21 |
---|---|
14 일관성 있는 협력 (0) | 2024.02.20 |
12 다형성 (1) | 2024.02.11 |
11 합성과 유연한 설계 (0) | 2024.02.09 |
10 상속과 코드 재사용 (0) | 2024.02.06 |