공부방/북스터디

[오브젝트] 13장

midcon 2024. 4. 8. 02:00

13장 정리

상속의 첫번째 용도는 타입 계층을 구현하는 것이고, 두번째 용도는 코드 재사용이다.
상속은 코드를 쉽게 재사용할 수 있는 방법을 제공하지만 부모 클래스와 자식 클래스를 강하게 결합시킨다.
타입 계층을 목표로 상속을 사용하면 다형적으로 동작하는 객체들의 관계에 기반해 확장 가능하고 유연한 설계를 얻을 수 있게 된다.
따라서 상속을 사용하는 일차적인 목표는 코드 재사용이 아닌 타입 계층을 구현하는 것이어야 한다.
동일한 메시지에 대해 서로 다르게 행동할 수 있는 다형적인 객체를 구현하기 위해서는 객체의 행동을 기반으로 타입 계층을 구성해야 한다.
상속의 가치는 이러한 타입 계층을 구현할 수 있는 쉽고 편안한 방법을 제공한다는데 있다.
타입 사이의 관계를 고려하지 않은 채 단순히 코드를 재사용하기 위해 상속을 사용해서는 안된다.


타입 

프로그래밍 언어 관점의 타입

프로그래밍 언어 관점에서 타입은 두 가지 목적을 위해 사용된다.

  • 타입에 수행될 수 있는 유효한 오퍼레이션의 집합을 정의한다.
  • 타입에 수행되는 오퍼레이션에 대해 미리 약속된 문맥을 제공한다.
    타입은 적용 가능한 오퍼레이션의 종류와 의미를 정의함으로써 코드의 의미를 명확하게 전달하고 개발자의 실수를 방지하기 위해 사용된다.

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

프로그래밍 언어 관점에서 타입은 호출 가능한 오퍼레이션의 집합을 정의한다면,

객체지향 프로그래밍에서 오퍼레이션은 객체가 수신할 수 있는 메시지를 의미한다.
따라서 객체의 타입이란 객체가 수신할 수 있는 메시지의 종류를 정의하는 것이다.
우리는 이미 퍼블릭 인터페이스라는 객체가 수신할 수 있는 메시지의 집합을 정의한 단어를 알고 있다.
객체지향에서는 객체가 수신할 수 있는 메시지를 기준으로 타입을 분류한다.
따라서 동일한 퍼블릭 인터페이스를 가지는 객체들은 동일한 타입으로 분류할 수 있다.
즉, 객체에게 중요한 것은 속성이 아니라 행동이다.
어떤 객체들이 동일한 상태를 가지고 있더라도 퍼블릭 인터페이스가 다르다면 이들은 서로 다른 타입으로 분류된다.

타입 계층

위 그림처럼 타입은 집합의 관점에서 더 세분화된 타입의 집합을 부분집합으로 포함할 수 있다.
다른 타입을 포함하는 타입은 다른 타입에 속해 있는 타입보다 좀 더 일반화된 의미를 표현할 수 있다.
반면 다른 타입에 속해 있는 타입은 좀 더 특수하고 구체적이다.
이것은 포함 관계로 연결된 타입 사이에 개념적으로 일반화와 특수화 관계가 존재한다는 것을 의미한다.
이를 그림으로 표현하면 아래와 같다.


타입 계층을 구성하는 두 타입 간의 관계에서 더 일반적인 타입을 슈퍼타입이라고 부르고 더 특수한 타입을 서브타입이라고 부른다.
이를 객체의 관점에서 보자면 일반화란 추상화를 의미하며, 특수화란 구체화를 의미한다고 할 수 있다.

객체지향 프로그래밍과 타입 계층

일반화와 특수화를 객체지향 프로그래밍 관점에서 보자면 더 일반적인 퍼블릭 인터페이스를 갖는 객체는 슈퍼타입이고 더 특수한 퍼블릭 인터페이스를 갖는 객체는 서브타입이라고 할 수 있다.
서브타입의 인스턴스는 슈퍼타입의 인스턴스로 간주될 수 있다.
이는 상속과 다형성의 관계를 이해하기 위한 출발점이며, 이 장의 핵심이다.

서브클래싱과 서브타이핑

그렇다면 퍼블릭 인터페이스가 더 특수하다는 것은 어떤 의미일까?
이제부터 타입 계층을 구현할 때 지켜야하는 제약사항을 클래스와 상속 관점에서 살펴보자.

언제 상속을 사용해야 하는가?

반복하지만 상속의 올바른 용도는 타입 계층을 구현하는 것이다.
마틴 오더스키는 다음 두 질문을 해보고 두 질문에 모두 '예'라고 답할 수 잇는 경우에만 상속을 사용하라고 조언한다.

  • 상속 관계가 is-a 관계를 모델링하는가?
  • 클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가?

슈퍼타입과 서브타입 관계에서는 is-a보다 행동 호환성이 더 중요하므로 설계 관점에서는 첫번째보다는 두번째 질문에 초점을 맞추는게 중요하다.

행동 호환성

행동의 호환 여부를 판단하는 기준은 클라이언트의 관점이다.
클라이언트가 기대하는 행동에 따라 두 타입을 타입 계층으로 묶을지 여부를 판단해야 한다.

클라이언트의 기대에 따라 계층 분리하기

클라이언트에 따라 인터페이스를 분리하면 변경에 대한 영향을 더 세밀하게 제어할 수 있게 된다.
요구 사항이 바뀌더라도 영향의 파급 효과를 효과적으로 제어할 수 있다.
이처럼 클라이언트의 기대에 따라 분리함으로써 변경에 의해 영향을 제어하는 설계 원칙을 인터페이스 분리 원칙이라고 부른다.

리스코프 치환 원칙

리스코프 치환 원칙을 한마디로 정의하면 아래와 같다.

  • 서브타입은 그것을 기반하는 슈퍼타입에 대해 대체 가능해야한다
    즉 자식 클래스가 부모 클래스와 행동 호환성을 유지함으로써 부모 클래스를 대체할 수 있도록 구현된 상속 관계만을 서브타이핑이라고 불러야한다.

클라이언트와 대체 가능성

어떤 모델의 유효성은 클라이언트의 관점에서만 검증 가능하다.

따라서 상속 관계에 있는 두 클래스 사이의 관계를 클라이언트와 떨어뜨려 놓고 판단해서는 안된다.
대체 가능성을 결정하는 것은 클라이언트다.

계약에 의한 설계와 서브타이핑

리스코프 치환 원칙과 계약에 의한 설계 사이의 관계를 다음과 같은 한 문장으로 요약할 수 있다.

  • 서브타입이 리스코프 치환 원칙을만족시키기 위해서는 클라이언트와 슈퍼타입 간에 체결된 '계약'을 준수해야 한다.

인상깊었던 점

"상속에 있어서 어떤 모델의 유효성은 클라이언트의 관점에서만 검증 가능하다."
이번 장에서는 타입을 프로그래밍 언어와 객체지향적 관점에서 바라보며 일반화와 특수화 관계를 설명하였다.
이후 일반화와 특수화를 추상화와 구체화로 서브타입과 슈퍼타입을 정의하고, 올바른 서브타입 슈퍼타입 관계인지 아닌지는 클라이언트의 기대에 따라 달라진다고 설명하면서 빌드업 과정을 거쳐서 설명한게 인상깊었다.
상속은 단순히 코드 재사용을 위해 사용하는게 아니라는걸 행동 호환성과 클라이언트의 연관성을 설명하면서 인터페이스 분리 원칙과 리스코프 치환 원칙과 함께 설명해 주는 부분이 좋았다.