오브젝트 Ch.13

Manx·2022년 6월 2일
0
post-thumbnail

'오브젝트: 코드로 이해하는 객체지향 설계' 3~4주차
분량 : Ch.11 ~ Ch.15
기간 : 22.5.21 ~ 22.6.4

Ch.13 서브클래싱과 서브타이핑

상속의 용도

  • 타입 계층 구현
  • 코드 재사용

타입 계층 구현

부모 클래스는 일반적인 개념을 구현하고 자식 클래스는 특수한 개념을 구현한다.
부모 클래스는 자식 클래스의 일반화이고, 자식 클래스는 부모 클래스의 특수화이다.

코드 재사용

상속을 사용하면 간단하게 코드를 재사용할 수 있지만, 결합도가 강하기 때문에 변경하기 어려운 코드를 얻게 될 확률이 높다.
-> 자세한 내용은 10장에서 정리했다.

상속을 사용하는 일차적인 목표는 타입 계층을 구현하는 것이다.
Ch.13에서는 올바른 타입 계층을 구성하는 원칙에 대해 알아본다.

타입의 세가지 구성요소

  • 심볼(symbol) : 타입에 이름을 붙인 것
  • 내연(intension) : 객체들이 가지는 공통적인 속성이나 행동
  • 외연(extension) : 타입에 속하는 객체들의 집합

객체지향 패러다임 관점의 타입

  • 개념 관점 : 타입이란 공통의 특징을 공유하는 대상들의 분류
  • 프로그래밍 언어 관점 : 오퍼레이션을 적용할 수 있는 인스턴스들의 집합

객체지향 패러다임의 관점
객체지향 프로그래밍에서 오퍼레이션은 객체가 수신할 수 있는 메시지를 의미함
객체의 타입이란 객체가 수신할 수 있는 메시지의 종류를 정의하는 것

객체가 수신할 수 있는 메시지의 집합을 가리키는 용어 : 퍼블릭 인터페이스

즉, 동일한 퍼블릭 인터페이스를 가지는 객체들은 동일한 타입으로 분류할 수 잇다.


서브타입과 슈퍼타입

타입 계층을 표현할 때는 더 일반적인 타입을 위쪽에, 더 특수한 타입을 아래쪽에 배치하는 것이 관례다.
타입 계층을 구성하는 두 타입 간의 관계에서 더 일반적인 타입을 슈퍼타임(supertype),
더 특수한 타입을 서브타입(subtype)이라고 부른다.

일반화는 다른 타입을 완전힣 포함하거나 내포하는 타입을 식별하는 행위 또는 그 행위의 결과를 가리킨다.
특수화는 다른 타입 안에 전체적으로 포함되거나 완전히 내포되는 타입을 식별하는 행위 또는 그 행위의 결과를 가리킨다.

서브타입의 인스턴스는 슈퍼타입의 인스턴스로 간주될 수 있다.
-> 상속과 다형성의 관계를 이해하기 위한 출발점


인터페이스 분리 원칙

마틴 오더스키는 다음 두 질문에 모두 '예'라고 답할 수 있는 경우에만 상속을 사용하라고 조언한다.

  • 상속 관계가 is-a 관계를 모델링하는가?
    ->"[자식 클래스]는 [부모 클래스]다" 라고 말해도 이상하지 않다면 상속을 사용할 후보로 간주할 수 있다.

  • 클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가?
    -> 클라이언트의 입장에서 부모 클래스와 자식 클래스의 차이점을 몰라야 한다. (자식 클래스와 부모 클래스 사이의 행동 호환성이라고 부름)

-> 첫 번째 질문보다는 두 번째 질문에 초점을 맞추는 것이 중요하다.

is-a 관계의 한계

  • 펭귄은 새다
  • 새는 날 수 있다

펭귄은 새지만 만약 새의 정의에 날 수 있다는 행동이 포함된다면 펭귄은 새의 서브타입이 될 수 없다. 만약 새의 정의에 날 수 있다는 행동이 포함되지 않으면 펭귄은 새의 서브타입이 될 수 있다.

타입 계층의 의미는 행동이라는 문맥에 따라 달라질 수 있다.
따라서 is-a 관계에 성립한다고 해도, 그저 후보로 생각해야 하는 것이다.


행동호환성

타입의 이름 사이에 개념적으로 어떤 연관성이 있다고 하더라도 행동에 연관성이 없다면 is-a 관계를 사용하지 말아야 한다.

  • 행동의 호환 여부를 판단하는 기준은 단순히 동일한 메서드를 구현하고 있는 것이 아닌 ,클라이언트의 관점이다.
  • 클라이언트가 두 타입에게 동일하게 행동하지 않을 것이라고 기대한다면 두 타입을 타입 계층으로 묶어서는 안된다.

펭귄이 새의 서브타입이 아닌 이유는 클라이언트 입장에서 모든 새가 날 수 있다고 가정하기 때문이다.

해결 방안

클라이언트의 기대에 맞게 상속 계층을 분리하는 것

날 수 있는 새와 날 수 없는 새를 분리한다.

public class Bird {
	...
}

public class FlyingBird extends Bird {
	public void fly() { ... }
}

public class Penguin extends Bird {
	...
}

// 날 수 있는 새한테만 보낸다.
public void flyBird(FlyingBird bird) {
	bird.fly();
}

만약 날 수 없는 새에게는 걸으라는 메시지를 보내려면 어떻게 해야 할까?
-> fly 오퍼레이션을 가진 Flyer 인터페이스와 walk 오퍼레이션을 가진 Walker 인터페이스로 분리할 수 있다.

만약 Penguin이 Bird의 코드를 재사용해야 한다면?
-> 합성으로 해결할 수 있다.

클라이언트의 기대에 따라 인터페이스를 분리함으로써 변경에 의해 영향을 제어하는 설계 원칙을 인터페이스 분리 원칙(Interface Segregation Principle, ISP)라고 한다.


서브클래싱과 서브타이핑

상속을 사용하는 두 가지 목적에 따라 두 가지로 나뉜다.
-> 서브클래싱과 서브타이핑

서브클래싱(subclassing)

  • 코드를 재사용할 목적으로 상속을 사용하는 경우.
  • 자식 클래스와 부모 클래스의 행동이 호환되지 않기 때문에 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 없다.
  • 구현 상속(implementation inheritance) or 클래스 상속(class inheritance)라고 부르기도 한다.

서브타이핑(subtyping)

  • 타입 계층을 구성하기 위해 상속을 사용하는 경우
  • 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 있다.
  • 부모는 자식의 슈퍼타입이다.
  • 자식은 부모의 서브타입이다.
  • 서브타이핑을 인터페이스 상속(interface inheritance)라고 부르기도 한다.

리스코프 치환 원칙 (Liskov Substitution Principle ,LSP)

서브타입은 그것의 기반 타입에 대해 대체 가능해야 한다.
클라이언트가 차이점을 인식하지 못한 채 기반 클래스의 인터페이스를 통해 서브클래스를 사용할 수 있어야 한다.

클라이언트의 입장에서 퍼블릭 인터페이스의 행동 방식이 변경되지 않는다면 클라이언트의 코드를 변경하지 않고도 새로운 자식 클래스와 협력할 수 있게 된다는 것이다.

리스코프 치환 원칙은 개방-폐쇄 원칙을 만족하는 설계를 위한 전제 조건이다.
리스코프 치환 원칙 위반 = 잠ㅐ적인 개방-폐쇄 원칙 위반

리스코프 치환 원칙을 따르는 설는 유연할뿐만 아니라 확장성이 높다.

서브타입과 계약

  • 서브타입에 더 강력한 사전조건을 정의할 수 없다.
  • 서브타입에 슈퍼타입과 같거나 더 약한 사전조건을 정의할 수 있다.
  • 서브타입에 슈퍼타입과 같거나 더 강한 사후조건을 정의할 수 있다.
  • 서브타입에 더 약한 사후조건을 정의할 수 없다.
profile
백엔드 개발자

0개의 댓글