Skip to main content
soso01 blog
  1. Posts/

오브젝트 13장 서브클래싱과 서브타이핑

·5 mins

13장 서브클래싱과 서브타이핑 #

01. 타입 #

  • 객체지향 프로그래밍에서 타입의 의미를 이해하려면 프로그래밍 언어 관점에서의 타입과 개념 관점에서의 타입을 함께 살펴볼 필요가 있다.

개념 관점의 타입 #

  • 개념 관점의 타입이란 우리가 인지하는 세상의 사물의 종류를 의미한다.
    • 우리가 인식하는 객체들에 적용하는 개념이나 아이디어를 가리켜 타입이라고 부른다.
  • 어떤 대상이 타입으로 분류될 때 그 대상을 타입의 인스턴스라고 한다. 일반적으로 인스턴스를 객체로 부름.

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

  • 프로그래밍 언어 관점에서 타입은 연속적인 비트에 의미와 제약을 부여하기 위해 사용한다.

객체지향 프로그래밍 관점의 타입 #

  • 객체지향 프로그래밍에서 오퍼레이션은 객체가 수신할 수 있는 메시지를 의미한다.
    • 따라서 객체의 타입이란 객체가 수신할 수 있는 메시지의 종류를 정의하는 것이다.
    • 객체지향 프로그래밍에서 타입을 정의하는 것은 객체의 퍼블릭 인터페이스를 정의하는 것과 동일하다.

02. 타입 계층 #

  • 타입 계층을 구성하는 두 타입 간의 관계에서 더 일반적인 타입을 슈퍼타입이라고 부르고, 더 특수한 타입을 서브타입이라고 부른다.
  • 객체의 의미를 정의하는 내연 관점에서 일반화란 어떤 타입의 정의를 좀 더 보편적이고 추상적으로 만드는 과정을 의미한다.
    • 반대로 특수화란 어떤 타입의 정의를 더 구체적이고 문맥 종속적으로 만드는 과정을 의미한다.
  • 객체지향의 퍼블릭 인터페이스의 관점에서 슈퍼타입과 서브타입을 다음과 같이 정의할 수 있다.
    • 슈퍼타입이란 서브타입이 정의한 퍼블릭 인터페이스를 일반화시켜 상대적으로 범용적이고 넓은 의미로 정의한 것이다.
    • 서브타입이란 슈퍼타입이 정의한 퍼블릭 인터페이스를 특수화시켜 구체적이고 좁은 의미로 정의한 것이다.

03. 서브 클래싱과 서브 타이핑 #

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

  • 상속의 올바른 용도는 타입 계층을 구현하는 것이다.
  • 다음 질문을 모두 충족할 때 사용하라
    • 상속 관계가 is-a 관계를 모델링하는가?
    • 클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무관한가?(행동 호환성)

is-a 관계 #

  • 펭귄은 새다.
    • 펭귄은 날 수 없는 새다.
    • Bird 클래스에 fly 메서드가 있다면 펭귄은 Bird의 서브타입이 될 수 없다.
    • 어휘적인 정의가 아니라 기대되는 행동에 따라 타입 계층을 구성해야 한다.
  • 타입 계층의 의미는 행동이라는 문맥에 따라 달라질 수 있다.

행동 호환성 #

  • 펭귄이 새가 아니라는 사실을 받아들이기 위한 출발점은 타입이 행동과 관련이 있다는 사실에 주목하는 것이다.
    • 타입의 이름 사이에 개념적으로 어떤 연관성이 있다고 하더라도 행동에 연관성이 없다면 is-a 관계를 사용하지 말아야 한다.
    • 결론은 두 타입 사이에 행동이 호환될 경우에만 타입 계층으로 묶어야 한다는 것이다.
  • 행동의 호환 여부를 판단하는 기준은 클라이언트 관점이다.
    • 클라이언트가 두 타입이 동일하게 행동할 것이라고 기대한다면 두 타입을 타입 계층으로 묶을 수 있다.

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

  • Bird → FlyingBird / Penguin
    • 슈퍼타입인 Bird에서 fly메서드를 제외하면 펭귄은 새의 서브타입이 될 수 있다.
    • fly 메서드가 있는 FlyingBird는 Bird의 서브타입으로 분리한다.
  • 설계가 꼭 현실 세계를 반영할 필요는 없다.

서브클래싱과 서브 타이핑 #

  • 서브 클래싱
    • 다른 클래스의 코드를 재사용할 목적으로 상속을 사용하는 경우를 가리킨다.
    • 자식 클래스와 부모 클래스의 행동이 호환되지 않기 때문에 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 없다.
  • 서브 타이핑
    • 타입 계층을 구성하기 위해 상속을 사용하는 경우를 가리킨다.
    • 자식 클래스와 부모 클래스의 행동이 호환되기 때문에 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 있다.
  • 서브타이핑 관계가 유지되기 위해서는 서브타입이 슈퍼타입이 하는 모든 행동을 동일하게 할 수 있어야 한다. → 행동호환성
  • 자식 클래스와 부모 클래스 사이의 행동호환성은 부모 클래스에 대한 자식 클래스의 대체 가능성을 포함한다.

04. 리스코프 치환 원칙 #

  • 서브타입은 그것의 기반 타입에 대해 대체 가능해야 한다.
    • 클라이언트가 차이점을 인식하지 못한 채 기반 클래스의 인터페이스를 통해 서브클래스를 사용해야 한다.
    • 리스코프 치환 원칙에 따르면 자식 클래스가 부모클래스와 행동 호환성을 유지함으로써 부모 클래스를 대체할 수 있도록 구현된 상속 관계만을 서브타이핑이라고 불러야 한다.

클라이언트와 대체 가능성 #

  • 자식 클래스가 부모 클래스를 대체하기 위해서는 부모 클래스에 대한 클라이언트의 가정을 준수해야 한다는 것을 강조
  • 클라이언트와 격리한 채로 본 모델은 의미 있게 검증하는 것이 불가능하다.
    • 어떤 모델의 유효성은 클라이언트의 관점에서만 검증 가능하다.
    • 클라이언트 관점에서 Square가 Rectangle의 서브타입이 아닐 수 있음.
      • Square의 setX, setY가 Rectangle과 호환이 안되는 예시

is-a 관계 다시 살펴보기 #

  • 클라이언트 관점에서 자식 클래스의 행동이 부모 클래스의 행동과 호환되지 않고 그로 인해 대체가 불가능하다면 어휘적으로 is-a라고 말할 수 있다고 하더라도 그 관계는 is-a가 아님.
    • 정사각형은 직사각형인가? 클라이언트에서 이를 동일하게 취급할 수 있을때만 그렇다.
  • is-a관계는 객체지향에서 중요한 것은 객체의 속성이 아니라 객체의 행동이라는 점을 강조한다.
  • 클라이언트를 고려하지 않은 채 개념과 속성의 측면에서 상속 관계를 정할 경우 리스코프 치환 원칙을 위반하는 서브클래싱에 이르게 될 확률이 높다.
  • 행동을 고려하지 않은 두 타입의 이름이 단순히 is-a로 연결 가능하다고 해서 상속 관계로 연결하지 마라.
  • 결론적으로 상속이 서브타이핑을 위해 사용될 경우에만 is-a관계다.

리스코프 치환 원칙은 유연한 설계의 기반이다. #

  • 리스코프 치환 원칙은 클라이언트가 어떤 자식 클래스와도 안정적으로 협력할 수 있는 상속 구조를 구현할 수 있는 가이드라인을 제공한다.
    • 새로운 자식 클래스를 추가하더라도 클라이언트의 입장에서 동일하게 행동하기만 하면 클라이언트를 수정하지 않고도 상속 계층을 확장할 수 있다.
    • 클라이언트의 입장에서 퍼블릭 인터페이스의 행동 방식이 변경되지 않는다면 클라이언트의 코드를 변경하지 않고도 새로운 자식 클래스와 협력할 수 있게 된다는 것이다.
  • 리스코프 치환 원칙을 따르는 설계는 유연할뿐만 아니라 확장성이 높다.

타입 계층과 리스코프 치환 원칙 #

  • 클래스 상속은 타입 계층을 구현할 수 있는 다양한 방법 중 하나일 뿐이다.
  • 구현 방법은 중요하지 않다. 핵심은 구현 방법과 무관하게 클라이언트의 관점에서 슈퍼타입에 대해 기대하는 모든 것이 서브타입에게도 적용돼야 하는 것이다.

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

  • 클라이언트와 서버 사이의 협력을 의무와 이익으로 구성된 계약의 관점에서 표현하는 것을 계약에 의한 설계라고 부른다.
    • 계약에 의한 설계는 클라이언트가 정상적으로 메서드를 실행하기 위해 만족시켜야 하는 사전조건과 메서드가 실행된 후에 서버가 클라이언트에게 보장해야 하는 사후조건, 메서드 실행 전과 실행 후에 인스턴스가 만족시켜야 하는 클래스 불변식의 세 가지 요소로 구성된다.
    • 리스코프 치환 원칙은 어떤 타입이 서브타입이 되기 위해서는 슈퍼타입의 인스턴스와 협력하는 ‘클라이언트’의 관점에서 서브타입의 인스턴스가 슈퍼타입을 대체하더라도 협력에 지장이 없어야 한다는 것을 의미한다.
  • 서브타입이 리스코프 치환 원칙을 만족시키기 위해서는 클라이언트와 슈퍼타입 간에 체결된 '계약'을 준수해야 한다.

서브타입과 계약 #

  • 서브타입에 더 강력한 사전조건을 정의할수는 없다.
  • 서브타입에 슈퍼타입과 같거나 더 약한 사전조건을 정의할 수 있다.
  • 서브타입에 슈퍼타입과 같거나 더 강한 사후조건을 정의할 수 있다.
  • 서브타입에 더 약한 사후조건을 정의할 수 없다.
  • → 서브타입에 조건은 더 강해질 순 없지만, 리턴하는 타입의 범위는 좁혀도 괜찮음. 슈퍼타입과 호환되기 때문
  • 부록 A 계약에 의한 설계를 참고