1. 개요
•
상속의 용도는 다음과 같다.
◦
타입 계층 구현 = 타입계층 안에서 부모클래스는 일반적인 개념을 구현하고 자식 클래스는 특수한 개념을 구현한다.
▪
부모 클래스는 자식 클래스의 일반화(generalization)
▪
자식 클래스는 부모 클래스의 특수화(specialization)
◦
코드 재사용
▪
간단한 선언만으로 부모 클래스의 코드를 재사용할 수 있다.
▪
재사용을 위해 상속을 사용하는 경우 부모 클래스와 자식 클래스가 강하게 결합되어 변경하기 어려운 코드를 얻게 될 확률이 높다.
2. 타입
2.1. 개념 관점의 타입
개념 관점에서 타입이란 우리가 인지하는 세상의 사물의 종류를 의미한다.
•
타입 : 타입은 사물을 분류하기 위한 틀로 사용된다.
•
인스턴스 : 어떤 대상이 타입으로 분류될 때의 대상을 가리켜 인스턴스라고 부른다.
•
객체 : 일반적으로 타입의 인스턴스를 가리켜 객체라고 부른다.
•
심볼 : 타입에 이름을 붙인 것.
•
내연(intension) : 타입의 정의로서 타입에 속하는 객체들이 가지는 공통적인 속성이나 행동
◦
예를 들어 프로그래밍 언어의 정의인 컴퓨터에게 특정한 작업을 지시하기 위한 어휘와 문법적 규칙의 집합 이 바로 내연이다.
•
외연(extension) : 타입에 속하는 객체들의 집합
◦
예를들어 프로그래밍 언어 타입의 경우에는 자바, 루비, 자바스크립트, C가 속한 집합이 외연을 구성한다.
2.2. 프로그래밍 언어 관점의 타입
•
프로그래밍 언어 관점에서의 타입은 연속적인 비트의 의미와 제약을 부여하기 위해 사용된다.
•
비트에 담긴 데이터를 문자열로 다룰지 정수로 다룰지는 전적으로 데이터를 사용하는 애플리케이션에 의해 결정된다.
•
따라서 프로그래밍 언어의 관점에서 타입은 비트 묶음에 의미를 부여하기 위해 정의된 제약과 규칙을 가리킨다.
•
프로그래밍 언어에서 타입은 두 가지 목적을 위해 사용된다.
◦
타입에 수행될 수 있는 유효한 오퍼레이션의 집합을 정의한다.
◦
타입에 수행되는 오퍼레이션에 대해 미리 약속된 문맥을 제공한다.
2.3. 객체지향 패러다임 관점의 타입
•
지금까지의 내용을 바탕으로 타입을 다음과 같은 두 가지 관점에서 정의할 수 있다.
◦
개념 관점에서 타입이란 공통의 특징을 공유하는 대상들의 분류다.
◦
프로그래밍 언어 관점에서 타입이란 동일한 오퍼레이션을 적용할 수 있는 인스턴스들의 집합이다.
•
객체지향 프로그래밍에서 오퍼레이션은 객체가 수신할 수 있는 메시지를 의미한다.
◦
따라서 객체의 타입이란 객체가 수신할 수 있는 메시지의 종류를 정의하는 것이다.
•
객체가 수신할 수 있는 메시지를 기준으로 타입을 분류하기 때문에 동일한 퍼블릭 인터페이스를 가지는 객체들은 동일한 타입으로 분류할 수 있다.
3. 타입 계층
3.1. 타입 사이의 포함관계
•
수학에서 집합은 다른 집합을 포함할 수 있다.
•
타입 역시 객체들의 집합이기 때문에 다른 타입을 포함하는 것이 가능하다
•
타입 안에 포함된 객체들은 좀 더 상세한 기준으로 묶어 새로운 타입을 정의하면 이 새로운 타입은 자연스럽게 기존 타입의 부분 집합이 된다.
•
타입 계층을 표현할 때는 더 일반적인 타입을 위쪽에 더 특수한 타입을 아래쪽에 배치하는 것이 관례다.
•
슈퍼타입 : 타입계층을 구성하는 두 타입 간의 관계에서 더 일반적인 타입
•
서브타입 : 타입계층을 구성하는 두 타입 간의 관계에서 더 특수한 타입
•
내연과 외연의 관점에서 일반화와 특수화를 정의해보자
◦
내연 관점
▪
일반화 = 어떤 타입의 정의를 좀 더 보편적이고 추상적으로 만드는 과정을 의미한다.
▪
특수화 : 어떤 타입의 정의를 좀 더 구체적이고 문맥 종속적으로 만드는 과정
◦
외연 관점
▪
일반화 : 특수한 타입의 인스턴스 집합을 표현하는 슈퍼셋(superset)
▪
특수화 : 일반적인 타입의 인스턴스 집합에 포함된 서브셋(subset)
3.2. 객체지향 프로그래밍과 타입 계층
•
객체의 타입을 결정하는 것은 퍼블릭 인터페이스다
•
일반적인 타입이란 비교하려는 타입에 속한 객체들의 퍼블릭 인터페이스보다 더 일반적인 퍼블릭 인터페이스를 가지는 객체들의 타입을 의미한다.
•
퍼블릭 인터페이스의 관점에서 슈퍼타입과 서브타입을 다음과 같이 정의할 수 있다.
◦
슈퍼타입 이란 서브타입이 정의한 퍼블릭 인터페이스를 일반화 시켜 상대적으로 범용적이고 넓은 의미 로 정의한 것이다.
◦
서브타입 이란 슈퍼타입이 정의한 퍼블릭 인터페이스를 특수화 시켜 상대적으로 범용적이고 좁은 의미 로 정의한 것이다.
4. 서브클래싱과 서브타이핑
4.1. 언제 상속을 사용해야 하는가?
•
올바른 용도는 타입 계층을 구현하는 것
•
아래 질문에 모두 yes라고 답할 수 있을때만 상속을 사용한다.
◦
상속관계가 is-a 관계를 모델링하는가?
▪
일반적으로 자식 클래스는 부모 클래스 라고 말해도 이상하지 않다면 상속을 사용할 후보로 간주할 수 있다.
◦
클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가?
▪
상속 계층을 사용하는 클라이언트 입장에서 부모 클래스와 자식 클래스의 차이점을 몰라야 한다.
▪
이를 자식 클래스와 부모 클래스 사이의 행동 호환성 이라 부른다.
4.2. is-a 관계
•
두 클래스가 어휘적으로 is-a 관계를 모델링할 경우에만 상속을 사용해야 한다.
•
하지만 is-a 관계가 직관적이고 명확하지는 않다.
•
다음의 예는 is-a 관계가 직관적이지 않음을 보여주는 예시이다.
•
타입 계층의 의미는 행동이라는 문맥에 따라 달라질 수 있다.
•
따라서 슈퍼타입과 서브타입 관계에서는 is-a보다 행동 호환성이 더 중요하다
4.3. 행동 호환성
•
타입 사이에 개념적으로 어떤 연관성이 있다고 하더라도 행동에 연관성이 없다면 is-a 관계를 사용하지 말아야 한다.
•
결론은 두 타입 사이에 행동이 호환되는 경우에만 타입 계층으로 묶어야 한다.
•
여기서 중요한 것은 행동의 호환 여부를 판단하는 기준은 클라이언트 관점이다.
•
클라이언트가 두 타입이 동일하게 행동할 것이라고 기대한다면 두 타입을 타입 계층으로 묶을 수 있다.
•
하지만 클라이언트가 두 타입이 동일하게 행동하지 않을 것이라고 기대한다면 두 타입을 타입 계층으로 묶어서는 안된다.
4.4. 클라이언트의 기대에 따라 계층 분리하기
•
클라이언트 기대에 따라 상속계층 분리 시
•
인터페이스 분리 원칙 = 인터페이스를 클라이언트 기대에 따라 분리함으로써 변경에 영향을 제어하는 설계원칙
4.5. 서브클래싱과 서브타이핑
•
서브클래싱(subclassing) : 다른 클래스의 코드를 재사용할 목적으로 상속을 사용하는 경우
◦
자식 클래스와 부모 클래스의 행동이 호환되지 않기 때문에 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 없다.
◦
구현상속(implementation inheritance) 또는 클래스 상속 (class inheritance)이라고 부른다
•
서브타이핑(subtyping) : 타입 계층을 구성하기 위해 상속을 사용하는 경우
◦
자식클래스와 부모 클래스의 행동이 호환되기 때문에 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 있다.
◦
서브타이핑을 인터페이스 상속 (interface inheritance) 이라고 한다
◦
서브타입이 슈퍼타입이 하는 모든 행동을 동일하게 할 수 있어야 한다
◦
행동 호환성을 만족 시켜야 한다.
•
서브클래싱과 서브타이핑을 나누는 기준은 상속을 사용하는 목적이다.
◦
자식이 부모의 코드를 재사용할 목적 = 서브 클래싱
◦
부모 대신 자식 인스턴스를 사용할 목적 = 서브 타이핑
•
서브 타이핑 관계가 유지되기 위해선 슈퍼타입이 하는 모든 행동을 동일하게 할 수 있어야 한다.
◦
어떤 타입이 다른 타입의 서브타입이 되지 위해선 행동 호환성을 만족시켜야함
•
자식 / 부모 클래스 사이의 행동 호환성은 부모 클래스에 대한 자식 클래스의 대체 가능성을 포함한다.
5. 리스코프 치환원칙
•
리스코프 치완 원칙
◦
서브 타입은 그것의 기반 타입에 대해 대체 가능해야 한다는 것
◦
클라이언트가 "차이점을 인식하지 못한 채 파생 클래스의 인터페이스를 통해 서브 클래스를 사용할 수 있어야 한다"는 것이다.
6. 계약에 의한 설계와 서브타이핑
•
계약에 의한 설계 (Design By Contract, DBC)
◦
클라이언트와 서버 사이의 협력을 의무와 이익으로 구성된 계약의 관점에서 표현하는 것
•
계약에 의한 설계의 세 가지 요소로 구성된다.
◦
사전조건(precondition) : 클라이언트가 정상적으로 메서드를 실행하기 위해 만족시켜야 하는 조건
◦
사후조건(postcondition) : 메서드가 실행된 후에 클라이언트에게 보장해야 하는 조건
◦
클래스 불변식(class invariant) : 메서드 실행 전과 실행 후에 인스턴스가 만족시켜야 하는 조건
•
리스코프 치환 원칙과 계약에 의한 설계 사이의 관계를 한 문장으로 요약하자면 다음과 같다.
◦
서브타입이 리스코프 치환 원칙을 만족시키기 위해서는 클라이언트와 슈퍼타입 간에 체결된 계약 을 준수해야한다.