0. 서론
상속의 첫 번째 용도는 타입 계층을 구현하는 것이다.
부모 클래스는 자식 클래스의 일반화(generalization)이고 자식클래스는 부모 클래스의 특수화(specialization)다.
상속의 두번째 용도는 코드 재사용이다.
재사용을 위해 상속을 사용할 경우 부모 클래스와 자식 클래스가 강하게 결합되기 때문에 변경하기 어려운 코드를 얻게 될 확률이 높다.
동일한 메시지에 대해 서로 다르게 행동할 수 있는 다형적인 객체를 구현하기 위해서는 객체의 행동을 기반으로 타입 계층을 구성해야 한다. 상속의 가치는 이러한 타입 계층을 구현할 수 있는 쉽고 편안한 방법을 제공한다는데 있다.
객체 지향 프로그래밍이란 클래스를 사용하는 프로그래밍 방식을 의미하고 객체기반 프로그래밍이란 클래스 없이 오직 객체만을 사용하는 프로그래밍 방식을 가리킨다.
1. 타입
개념 관점의 타입
개념 관점에서 타입이란 우리가 인지하는 세상의 사물의 종류를 의미한다.
어떠한 대상이 타입으로 분류될 때 그 대상을 타입의 인스턴스라고 부르고, 일반적으로 타입의 인스턴스를 객체라고 부른다.(공통의 특징을 공유하는 대상들의 분류)
프로그래밍 언어 관점의 타입
프로그래밍 언어 관점에서 타입은 연속적인 비트에 의미와 제약을 부여하기 위해 사용된다.
적용 가능한 오퍼레이션의 종류와 의미를 정의함으로써 코드의 의미를 명확하게 전달하고 개발자의 실수를 방지하기 위해 사용된다.
객체지향 패러다임 관점의 타입
객체지향 프로그래밍에서 타입을 정의하는 것은 객체의 퍼블릭 인터페이스를 정의하는 것과 동일하다.
객체의 퍼블릭 인터페이스가 객체의 타입을 결정한다.
동일한 퍼블릭 인터페이스를 제공하는 객체들은 동일한 타입으로 분류된다. 객체의 타입을 결정하는 것은 내부의 속성이 아니라 외부에 제공하는 행동
타입계층
객체지향 프로그래밍과 타입 계층
슈퍼 타입이란 서브타입이 정의한 퍼블릭 인터페이스를 일반화시켜 상대적으로 범용적이고 넓은 의미로 정의한 것이다.
서브타입이란 슈퍼 타입이 정의한 퍼블릭 인터페이스를 특수화시켜 상대적으로 구체적이고 좁은 의미로 정의한 것이다.
서브 클래싱과 서브 타이핑
상속의 올바른 용도는 타입 계층을 구현하는 것이다.
상속은 모두가 "예"라고 답할 수 있는 경우에 상속을 사용해야 한다.
상속 관계가 is-a 관계를 모델링하는가?
- 자식 클래스는 부모 클래스다라고 말해도 이상하지 않다면 상속을 사용한다.
클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가?
- 부모 클래스와 자식 클래스의 차이점을 몰라야 한다. (행동 호환성)
is - a 관계
새에게 날 수 있다는 행동을 기대하지 않고 단지 울음소리를 낼 수 있다는 행동만 기대한다면 새와 펭귄을 타입 계층으로 묶어도 무방하다.
이 상황은 문맥에 따라 달라질 수 있기 때문에 is-a관계보다는 행동 호환성이 더 중요하다.
하지만, is-a 관계가 생각처럼 직관적이고 명쾌한 것은 아니다.
행동 호환성
행동의 호환 여부를 판단하는 기준은 클라이언트의 관점이다.
클라이언트의 기대에 따라 계층 분리하기
날 수 있는 새와 날 수 없는 새를 명확하게 구분할 수 있게 상속 계층을 분리하면 서로 다른 요구사항을 가진 클라이언트를 만족시킬 수 있다.
- 클라이언트 기대에 따라 상속계층을 분리
문제를 해결하는 다른 방법은 클라이언트에 따라 인터페이스를 분리하는 것이다.
불안정한 상속 계층을 껴안고 가는 것보다 슈퍼클래스를 재사용 가능하도록 수정하는 것이 더 좋은 방법이다.
인터페이스를 클라이언트의 기대에 따라 분리함으로써 변경에 의한 영향을 제어하는 설계 원칙을 인터페이스 분리 원칙(ISP)이라고 부른다.
서브 클래싱과 서브 타이핑
서브 클래싱
- 다른 클래스의 코드를 재사용할 목적으로 상속을 사용하는 경우 자식 클래스와 부모 클래스의 행동이 호환되지 않기 때문에 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 없다. (구현 상속, 클래스 상속)
서브 타이핑
- 타입 계층을 구성하기 위해 상속을 사용하는 경우를 가리킨다.
- 자식 클래스와 부모 클래스의 행동이 호환되기 때문에 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 있다. 이때 부모 클래스는 자식 클래스의 슈퍼 타입이 되고 자식 클래스는 부모 클래스의 서브 타입이 된다. (인터페이스 상속)
인터페이스 상속 관계를 갖는 경우 프로그램에는 슈퍼 타입으로 정의하지만 런타임에 서브타입의 객체로 대체할 수 있다.
서브 타이핑 관계가 유지되기 위해서는 서브타입이 슈퍼 타입이 하는 모든 행동을 동일하게 할 수 있어야 한다.
어떤 타입이 다른 타입의 서브타입이 되기 위해서는 행동 호환성을 만족시켜야 한다.
자식 클래스와 부모 클래스 사이의 행동 호환성은 부모 클래스에 대한 자식 클래스의 대체 가능성을 포함한다.
리스 코프 치환 원칙
서브타입은 그것의 기반 타입에 대해 대체 가능해야 한다 클라이언트가 차이점을 인식하지 못한 채 파생 클래스의 인터페이스를 통해 서브클래스를 사용할 수 있어야 한다.
자식 클래스가 부모 클래스와 행동 호환성을 유지함으로써 부모 클래스를 대체할 수 있도록 구현된 상속 관계만을 서브 타이핑이라고 불러야 한다.
클라이언트와 대체 가능성
상속 관계에 있는 두 클래스 사이의 관계를 클라이언트와 떨어트려 놓고 판단하지 말라고 속삭인다.
상속 관계는 클라이언트의 관점에서 자식 클래스가 부모 클래스를 대체할 수 있을 때만 올바르다.
Is-a 관계 다시 살펴보기
상속이 서브 타이핑을 위해 사용될 경우에만 is-a관계다. 서브 클래싱을 구현하기 위해 상속을 사용했다면 is-a 관계라고 말할 수 없다.
리스 코프 치환 원칙은 유연한 설계의 기반이다.
- 의존성 역전 원칙 (DIP)
- 구체 클래스인 Movie와 OverlappedDiscountPolicy 모두 추상 클래스인 DiscountPolicy에 의존한다.
- 상위 수준의 모듈인 Movie와 하위 수준의 모듈인 OverlappedDiscountPolicy는 모두 추상 클래스인 discountPolicy에 의존한다. 따라서 이 설계는 DIP를 만족한다.
- 리스코프 치환 원칙 (LSP)
- DiscountPolicy와 협력하는 Movie의 관점에서 DiscountPolicy 대신 OverlappedDiscountPolicy와 협력하더라도 아무런 문제가 없다.
- 다시 말해서 OverlappedDiscountPolicy는 클라이언트에 대한 영향 없이도 DiscountPolicy를 대체할 수 있다. 따라서 이 설계는 LSP를 만족한다.
- 개방 폐쇄 원칙 (OCP)
- 중복 할인 정책이라는 새로운 기능을 추가하기 위해 DiscountPolicy의 자식 클래스인 OverlappedDiscountPolicy를 추가하더라도 Movie에는 영향을 끼치지 않는다.
- 다시 말해서 기능 확장을 하면서 기존 코드를 수정할 필요는 없다. 따라서 이 설계는 OCP를 만족한다.
계약에 의한 설계와 서브 타이핑
계약에 의한 설계는 클라이언트가 정상적으로 메서드를 실행하기 위해 만족시켜야 하는 사전조건과 메서드가 실행된 후 서버가 클라이언트에게 보장해야 하는 메서드 실행 전과 실행 후에 인스턴스가 만족시켜야 하는 클래스 불변식의 세 가지 요소로 구성된다.
리스 코프 치환 원칙과 계약에 의한 설계 사이의 관계를 다음과 같은 한 문장으로 요약할 수 있다. 서브타입이 리스코프 치환 원칙을 만족시키기 위해서는 클라이언트와 슈퍼 타입 간에 체결된 계약을 준수해야 한다.
서브타입과 계약
서브타입에 더 강력한 사전 조건을 정의할 수 없다. 서브타입에 더 약한 사후 조건을 정의할 수 없다
슈퍼 타입을 치환할 수 없기 때문?
서브타입에 슈퍼 타입과 같거나 더 약한 사전조건을 정의할 수 있다. 서브타입에 슈퍼타입과 같거나 더 강한 사후 조건을 정의할 수 있다.