Table of contents
Open Table of contents
Intro
상속이 사용되는 두 가지 용도
타입 계층 구현 부모 클래스에서는 일반화하고 자식 클래스는 부모 클래스를 특수화한다.
코드 재사용 간단한 선언만으로 부모 클래스의 코드를 재사용할 수 있다. 재사용을 위해 상속을 사용할 경우 부모 클래스와 자식 클래스가 강하게 결합되기 때문에 변경하기 어려운 코드를 얻게될 확률이 높다.
상속은 동일한 메시지에 대해 서로 다르게 행동할 수 있는 다형적인 객체를 구현하기 위해 객체의 행동을 기반으로 타입 계층을 구성하기 위해 사용해야 한다.
타입
개념 관점의 타입
- 우리가 인지하는 세상의 사물의 종류를 의미한다.
- 어떤 대상이 타입으로 분류될 때 그 대상을 타입의 인스턴스(객체)라고 부른다.
타입의 구성요소
- 심볼(symbol): 타입을 구분하는 식별자
- 내연(intension)[본질적 특성]: 타입을 정의하는 특성, 공통적인 속성이나 행동을 가리킨다.
- 외연(extension)[개념이 적용될 수 있는 대상들의 집합]: 타입에 속하는 인스턴스(객체)의 집합
프로그래밍 언어 관점의 타입
프로그래밍 언어 관점에서 타입은 비트 묶음의 의미를 부여하기 위해 정의된 제약과 규칙을 가리킨다.
- 타입에 수행될 수 있는 유효한 오퍼레이션의 집합을 정의한다.
- 타입에 수행되는 오퍼레이션에 대해 미리 약속된 문맥을 제공한다.
객체지향 패러다임 관점의 타입
타입을 두 가지 관점에서 정의할 수 있다.
- 개념 관점에서 타입이란 공통의 특징을 공유하는 대상들의 분류다.
- 프로그래밍 언어 관점에서 타입이란 동일한 오퍼레이션을 적용할 수 있는 인스턴스들의 집합이다.
객체지향에서 타입을 정의하는 것은 퍼블릭 인터페이스를 정의하는 것과 동일하다. 동일한 퍼블릭 인터페이스를 제공하는 객체들은 동일한 타입으로 분류된다.
타입 계층
타입 사이의 포함관계
- 타입 계층을 구성하는 두 타입간의 관계에서 더 일반적인 타입을 슈퍼타입 이라 부르고 더 특수한 타입을 서브타입 이라고 부른다.
- 객체를 정의를 의미하는 내연 관점에서 일반화는 어떤 타입의 정의를 보편적이고 추상적으로 만드는 과정 을 의미한다. 특수화는 어떤 타입의 정의를 좀 더 구체적이고 문맥 종속적으로 만드는 괒어을 의미한다.
슈퍼타입
- 집합이 다른 집합의 모든 멤버를 포함한다.
- 타입 정의가 다른 타입보다 좀 더 일반적이다.
서브타입
- 집합에 포함되는 인스턴스들이 더 큰 집합에 포함된다.
- 타입 정의가 다른 타입보다 좀 더 구체적이다.
객체지향 프로그래밍과 타입 계층
퍼블릭 인터페이스 관점에서 슈퍼타입과 서브타입
슈퍼타입 서브타입이 정의한 퍼블릭 인터페이스를 일반화시켜 상대적으로 범용적이고 넓은 의미로 정의한 것이다. 서브타입 슈퍼타입이 정의한 퍼블릭 인터페이스를 특수화시켜 상대적으로 구체적이고 좁은 의미로 정의한 것이다.
서브타입의 인스턴스는 슈퍼타입의 인스턴스로 간주될 수 있다.
덕타이핑
- 사람이 오리처럼 행동하면 오리로 봐도 무방하다.
- 타입을 미리 정의하지 않고 객체가 실제로 수행하는 행동에 따라 객체의 타입을 결정하는 방식을 덕 타이핑이라고 한다.
One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural subtyping”. In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project.
타입스크립트의 타입 체크는 값의 형태(shape)에 초점을 맞춘다. 이를 때로는 “덕 타이핑” 또는 “구조적 서브타이핑”이라고 한다. 타입스크립트에서 인터페이스는 이러한 타입을 명명하는 역할을 하며 코드 내부와 프로젝트 외부의 코드와의 계약을 정의하는 강력한 방법이다.
interface LabeledValue {
label: string;
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
서브클래싱과 서브타이핑
어떤 타입이 다른 타입의 서브타입이 되기 위해서는 어떤 조건을 만족해야 할까?
서브타입의 퍼블릭 인터페이스가 슈퍼타입의 퍼블릭 인터페이스보다 더 특수하다는 것은 어떤 의미일까?
언제 상속을 사용해야 하는가?
타입 계층을 구현할 때
- 상속 관계가 is-a 관계를 모델링하는가?
- 클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가?
is-a 관계
어떤 타입 S가 다른 타입의 T의 일종이라면 “타입 S는 타입 T이다(S is-a T)“라고 말할 수 있다.
펭귄은 새다, 새는 날 수 있다. 하지만 펭귄은 날 수 없다. 어휘적인 정의가 아니라 기대되는 행동에 따라 타입 계층을 구성해야 한다.
행동 호환성
행동의 호환 여부를 판단하는 기준은 클라이언트의 관점 이라는 것이다.
합성을 사용해서 행동을 분리하고 인터페이스를 정의하면 클라이언트의 기대에 따라 인터페이스를 분리할 수 있다.
인터페이스를 클라이언트의 기대에 따라 분리함으로써 변경에 의해 영향을 제어하는 설계 원칙을 인터페이스 분리 원칙 이라고 부른다.
서브클래싱과 서브타이핑
- 서브클래싱(subclassing): 다른 클래스의 코드를 재사용할 목적으로 상속을 사용하는 경우를 가리킨다. -> 클래스 상속
- 서브타이핑(subtyping): 타입 계층을 구성하기 위해 상속을 사용하는 경우를 가리킨다. -> 인터페이스 상속
리스코프 치환 원칙
상속 관계로 연결한 두 클래스가 서브타이핑 관계를 만족하기 위해서는 다음의 조건을 만족시켜야 한다.
서브타입은 그것의 기반 타입에 대해 대체 가능해야 한다.
결론적으로 상속이 서브타이핑을 위해 사용될 경우에만 is-a 관계다. 서브클래싱을 구현하기 위해 상속을 사용했다면 is-a 관계라고 말할 수 없다.
리스코프 치환 원칙은 유연한 설계의 기반이다
- 의존성 역전 원칙: 구체 클래스인
Movie
와OverlappedDiscountPolicy
모두 추상 클래스인DiscountPolicy
에 의존한다 상위 수준의 모듈인Movie
와 하위 수준의 모듈인OverlappedDiscountPolicy
는 모두 추상 클래스인DiscountPolicy
에 의존한다. 따라서 이 설계는 DIP를 만족한다. - 리스코프 치환 원칙:
DiscountPolicy
와 협력하는Movie
의 관점에서DiscountPolicy
대신OverlappedDiscountPolicy
와 협력하더라도 아무런 문제가 없다. 다시 말해서OverlappedDiscountPolicy
는 큘라이언트에 대한 영향 없이도DiscountPolicy
를 대체할 수 있댜 따라서 이 설계는 LSP를 만족한다. - 개방-페쇄 원칙: 중복 할인 정책이라는 새로운 기능을 추가하기 위해
DiscountPolicy
의 자식 클래스인OverlappedDiscountPolicy
를 추가하더라도Movie
에는 영향을 끼치지 않는다. 다시 말해서 기능 확장을 하면서 가존 코드를 수정할 필요는 없다. 따라서 이 설계는 OCP를 만족한다.
계약에 의한 설계와 서브 타이핑
클라이언트와 서버 사이의 협력을 의무(obligation)와 이익(benefit)으로 구성된 계약의 관점에서 표현하는 것을 계약에 의한 설계 라고 부른다.
계약의 의한 설계 구성 요소
- 사전조건(precondition): 클라이언트가 메서드를 호출하기 전에 메서드가 요구하는 조건
- 사후조건(postcondition): 메서드가 종료된 후에 만족되어야 하는 조건
- 불변식(invariant): 메서드 호출 전후에 항상 참이어야 하는 조건