선생님, 개발을 잘하고 싶어요.

[오브젝트] 챕터 13 - 서브클래싱과 서브타이핑 본문

일상/책 리뷰

[오브젝트] 챕터 13 - 서브클래싱과 서브타이핑

알고싶은 승민 2022. 3. 23. 07:00

단순히 코드 재사용을 위한 상속에 서브클래싱이라는 이름을 붙히고 타입 계층을 만들기 위해서 사용하는 상속에 서브타이핑이라는 이름이 있다는 사실을 알게된 챕터입니다.

이런 구분을 알게 되니까 LSP의 진정한 의미에 대해서 조금은 알게된 것 같습니다. 원래는 “당연히 부모 타입에 자식 타입 인스턴스를 넣고 사용할 수 있는데 뭔 소리야?”라고 생각하고 대수롭지 않게 여긴 설계 원칙이였는데요. 이번 챕터를 통해서 객체 지향 설계의 정수가 아닌가? 라는 생각이 들게 되었습니다.

우선 LSP와 다형성의 관계가 매우 재밌었습니다. 객체 지향을 처음 배울 때 부터 우리는 다형성에 대해서 배웠습니다만 다형성을 단순히 Overloading 정도로 생각하고 있었던 것 같아요.

타입이라는 개념으로 역할을 정의하고, 코드를 OCP하게 만들기 위해서는 LSP가 반드시 지켜져야 한다는 사실을 알게되었습니다. 타입 계층을 만들려면, 무조건 구체적인 타입이 일반적인 타입의 인스턴스로 활용될 수 있어야 한다는 생각을 하게 되었네요.


  • 도입타입 계층안 에서 부모 클래스는 일반적인 개념을 나타내고 자식 클래스는 특수한 개념을 나타낸다. 부모는 자식의 일반화이고 자식은 부모의 특수화라고 생각할 수 있다.따라서 우리가 상속을 사용하는 목표는 타입 계층을 구현하는 것이 되어야 한다. 다른 말로 하면 다형적인 객체를 구현하기 위해서 객체의 행동을 기반으로 타입 계층을 구성해야 한다는 것이다.</aside>
  • <aside> 💡 이번 장에서는 올바른 타입 계층을 구성하는 원칙을 알아보고자 한다.
  • 상속을 사용하면 간단하게 자식 클래스에서 부모 클래스의 코드를 재사용할 수 있다 하지만 단순히 이런 용도로 상속을 사용하면 자식 클래스와 부모 클래스 간의 강한 결합도로 인해서 변경하기 어려운 코드가 만들어진다.
  • 상속은 두 가지 목적으로 사용된다. 하나는 타입 계층을 구현하는 것이고 나머지 하나는 코드 재사용이다.
  • 타입
    • 개념 관점의 타입
      • 심볼: 타입의 이름
      • 내연: 타입이 가진 속성과 행동 집합
      • 외연: 타입에 속하는 객체 집합
    • 프로그래밍 언어적 관점의 타입
      • 비트 묶음에 의미를 부여하기 위해 정의된 제약과 규칙
      • 타입에 수행될 수 있는 유효 오퍼레이터 정의
      • 타입에 수행되는 오퍼레이션에 대해 미리 약속된 문맥 제공
      • 타입은 적용 가능한 오퍼레이션 종류와 의미를 정의
    • 객체지향 패러다임 관점의 타입
      • 객체의 퍼블릭 인터페이스를 정의하는 것
      <aside> 💡 동일한 퍼블릭 인터페이스라면 동일한 타입으로 본다.
      • 타입을 결정하는 건 객체가 외부에 제공하는 행동
    • </aside>
  • 타입 계층
    • 타입 사이 포함관계
      • 구체적인 타입은 일반적인 타입의 부분 집합이다. (타입은 일반화 - 특수화 관계를 가진다.)
      <aside> 💡 일반적이다 특수하다는 두 객체 간의 상대적인 개념이다.
      • 슈퍼타입: 일반적인 타입
      • 서브타입: 특수한 타입
      • 특수한 타입에 속한 인스턴스는 동시에 더 일반적인 타입의 인스턴스이기도 하다. (업캐스팅)
    • </aside>
    • 객체지향 프로그래밍과 타입 계층
      • 서브타입의 인스턴스는 슈퍼타입의 인스턴스로 간주될 수 있다.
      • 상속 + 다형성의 관계를 이해하기 위한 시작점
  • 서브클래싱과 서브타이핑
    • 상속은 is-a 관계일 때 사용하라는 격언은 어휘적으로 낚시다.
      • 정사각형은 직사각형인가? (Square extends Rectangle)? 유명한 오류
    • 클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가? 가 핵심이다!
      • 행동 호환성이라고 한다.
    • 행동의 호환을 판단하는 기준은 항상 클라이언트 관점이다.
    • 클라이언트의 기대에 따라 계층 분리하기
      • 클라이언트 기대에 맞게 상속 계층을 분리하라.
      • 클라이언트에 따라 인터페이스를 분리하라.
      • 인터페이스는 클라이언트가 기대하는 바에 따라 분리돼야 한다.
      • 인터페이스라는 건 대부분 클라이언트의 요구가 바뀌면서 변경된다.
      • 이 처럼 클라이언트의 기대에 따라 인터페이스를 분리하여 변경에 의한 영향을 제어하는 설계 원칙을 인터페이스 분리 원칙이라고 한다(ISP)
      • 요구사항 속에서 클라이언트가 기대하는 행동에 집중하라는 것이다.
    • 서브클래싱: 코드 재사용 목적을 상속 활용 - 구현 상속, 클래스 상속
    • 서브타이핑: 타입 계층을 구성하기 위해 상속 활용 - 인터페이스 상속
    <aside> 💡 우리는 서브타이핑, 즉 인터페이스 상속을 선호해야한다. 타입 계층을 구현해야 한다.
  • </aside>
  • 리스코프 치환 원칙 (LSP)</aside>
    • 부모 클래스를 대체할 수 있도록 구현된 상속 관계만을 서브타이핑이라 함
    • 이를 만족하기 위해서는
      • 부모 클래스에 대한 클라이언트의 가정을 자식 클래스가 준수해야함 을 의미한다.
    • 모델의 유효성은 클라이언트 관점에서만 검증 가능
    • 대체 가능성을 결정하는 건 클라이언트다.
    • LSP를 지키면
      • 클라이언트가 어떤 자식 클래스와도 안정적으로 협력할 수 있는 상속 구조를 구현할 수 있는 가이드라인 제공
      • 유연할뿐만 아니라 확장성 높다.
    • LSP 위반은 잠재적 OCP 위반이다.
    • 클라이언트 관점에서 서로 다른 구성 요소를 동일하게 다뤄야 한다면? → LSP 원칙을 떠올리자.
  • <aside> 💡 서브타입은 그것의 기반 타입에 대해 대체 가능해야 한다.
  • 계약에 의한 설계와 서브타이핑
    • 부모 클래스에 적용된 계약
      • 사전조건 (precondition)
      • 사후조건 (postcondition)
      • 클래스 불변식 (class invariant)
    • 자식 클래스를 만들려면 부모 클래스 - 클라이언트간 약속을 깨지 않아야 한다.
      • 부모와 같거나 보다 약한 사전조건
      • 부모와 같거나 보다 강한 사후조건
Comments