서브 클래싱과 서브 타이핑

상속

  • 상속의 용도는 1. 타입 계층을 구현하는 것이다.
  • 타입 계층의 관점에서 부모 클래스는 자식 클래스의 일반화이고 자식클래스는 부모 클래스의 특수화다.
  • 그 다음 용도는 2.코드 재사용

1. 타입

  • OOP에서 타입은 프로그래밍 언어 관점, 개념 관점 모두 알아야 한다.

1.1. 프로그래밍 언어 관점

  • 타입에 수행될 수 있는 유요한 오퍼레이션 집합을 정의
  • 타입에 수행되는 오퍼레이션에 대해 미리 약속된 문맥을 제공

1.2. 개념 관점

  • 인지하는 세상의 사물의 종류를 의미한다. 우리가 인식하는 객체들에 적용하는 개념이나 아이디어를 가리켜 타입이라고 부른다.
  • 이는 심볼, 내연, 외연으로 구성된다.
    • 심볼 : 타입에 이름을 붙인 것
    • 내연 : 객체들이 가지는 공통적인 속성이나 행동
    • 외연 : 타입에 속하는 객체들의 집합
  • 이를 정리하면 두 가지 관점에서 타입을 정의할 수 있다.
    1. 개념 관점에서 타입이란 공통의 특징을 공유하는 대상들의 분류
    2. 프로그래밍 언어 관점에서 동일한 오퍼레이션을 적용할 수 있는 인스턴스들의 집합
  • OOP 관점에서 이를 조합하면 (2)는 호출 가능한 오퍼레이션의 집합, 퍼블릭 인터페이스라고 부를 수 있다.
  • (1)의 관점에서 타입은 공통의 특성을 가진 객체들을 분류하기 위한 기준이다. 공통의 기준이란 객체가 수신할 수 있는 메시지를 기준으로 타입을 분류하기 떄문에 동일한 퍼블릭 인터페이스를 가지는 객체들을 동일 타입으로 묶을 수 있다.

2. 타입 계층

  • 타입 계층을 구성하는 타입 관계 간에서 더 일반적인 타입을 슈퍼타입이라고 부른다.
  • 더 특수한 타입을 서브타입이라고 부른다.
  • 일반화란 어떤 타입의 정의를 더 보편적이고 추상적으로 만드는 과정을 의미한다.
  • 특수화한 어떤 타입의 정의를 좀 더 구체적으로 문맥 종속적으로 만드는 과정을 의미한다.

OOP에서 타입 계층

  • 일반적 타입이란 비교하는 타입에 속한 객체들의 퍼블릭 인터페이스보다 더 일반적인 퍼블릭 인터페이스를 가지는 객체들의 타입을 의미한다.

3. 서브클래싱, 서브 타이핑

  • 타입을 구현하는 일반적 방법은 클래스를 이용하는 것이고 타입 계층을 구현하는 일반적 방법은 상속이 있다.
  • 상속을 통해서 타입 계층을 구현한다는 것은 부모 클래스가 슈퍼타입의 역할을, 자식 클래스가 서브타입의 역할을 수행하는 것이다.
  • 상속은 두 가지가 충족 되어야 한다.
    1. is-a
    2. 행동 호환성

1. is-a

  • 두 클래스가 is-a 관계라면 상속을 이요할 수 있다.
  • 타입 S가 다른 타입 T의 일종이라면 “S는 T이다.”라고 말할 수 있다.

2. 행동 호환성

  • 슈퍼, 서브 타입 관계에서는 is-a보다 행동 호환성이 더 중요하다.
  • 타입의 이름 사이에 개념적으로 연관성이 있더라도 행동 연관성이 없으면 is-a를 사용하면 안된다.
  • 행동 호환 여부는 클라이언트 관점으로 보는 것이다.
  • 결론적으로 행동이 호환되어야만 타입 계층으로 묶어야 한다는 것이다.
    • 간혹 슈퍼 타입의 특정 행동이 필요하지 않을 수도 있다. 이 때는 구현을 비우거나 해서 모면할 수는 있지만 역시나 답은 아니다.
    • 차라리 클라이언트에 따라서 인터페이스를 분리하는 것이 나을 수도 있다.
    • 더 좋은 방법은 합성이 있다.
    • 이처럼 인터페이스를 클라이언트의 기대에 따라 분리해서 변경에 의해서 영향을 제어하는 설계 원칙을 ISP(Interface Segregation Principal)라고 한다.
  • 이런 경우 현실을 그대로 복사하기 보다는 요구사항을 실용적으로 수용하는 것이 맞다.

  • 다시 돌아가서 상속은 타입 계층을 구성하는데 여러 문제가 있다.
  • 여기서 볼 개념이 앞서 기술한 서브 클래싱, 서브 타이핑이다.
  • 서브클래싱(subclassing) : 다른 클래스의 코드를 재사용할 목적으로 상속을 사용하는 경우를 가리킨다. 자식 클래스와 부모 클래스의 행동이 호환되지 않기에 서로 대체하기는 어렵다. 구현 상속(implementation inheritance) 또는 클래스 상속(class inheritance)라고 한다.
  • 서브타이핑(subtyping) : 타입 계층을 구성하기 위해서 상속을 사용하는 경우를 가리킨다. 서브 타이핑은 자식 클래스와 부모 클래스 간 행동이 호환되기에 자식이 부모를 대체할 수 있다. 부모 클래스는 자식의 슈퍼 타입, 자식은 부모의 서브 타입이 된다. 인터페이스 상속(interface inheritance) 라고 부른다.
  • 서브 타이핑 관계가 유지되기 위해서는 서브가 슈퍼가 하는 모든 행동을 동일하게 할 수 있어야 한다.
  • 즉, 어떤 타입이 다른 타입의 서브 타입이 되려면 행동호환성(behavioral substitution)을 만족시켜야 한다.
  • 행동 호환성은 부모에 대한 자식의 대체 가능성을 포함한다.

4. LSP, Liskov Substitution Principal

  • 올바른 상속 관계의 특징을 정의하게 위해서 정해진 원칙이다.
  • 자식 클래스가 부모 클래스와 행동 호환성을 유지함으로써 부모 클래스를 대체할 수 있도록 구현된 상속 관계만을 서브타이핑이라고 불러야 한다.
  • 보통 리스코프 치환 원칙을 위반하면 서브 타이핑이 아니라 서브 클래싱이다.
  • 또한 리스코프 치환 원칙은 유연한 설계의 기반이 된다.
    • 새로운 자식 클래스를 추가하더라도 클라이언트 입장에서 동일하게 행동한다면
    • 클라이언트 수정 없이 상속 계층을 확장할 수 있다.
    • 또한 LSP는 OCP를 위한 전제 조건이 된다.

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

  • 클라이언트와 서버 사이의 협력을 의무, 이익으로 구성된 계약의 관점에서 표현하는 것을 계약에 의한 설계라고 부른다.
  • 계약에 의한 설계는 아래 3가지로 구성된다.
  • 클라이언트가 지켜야하는 사전조건
  • 메소드 실행 이후 서버가 클라이언트에 보장해야 하는 사후조건
  • 메소드 실행 전과 실행 후 인스턴스가 만족시켜야 하는 클래스 불변식
  • 서브타입이 LSP를 만족하기 위해서는 Client, SuperType간 체결된 계약을 준수해야 한다.
  • LSP는 서브 타입이 슈퍼를 대체할 수 있더야 하고, 클라이언트가 차이점을 인식하지 못한 채 슈퍼타입의 인터페이스로 서브 타입과 협력할 수 있어야 한다고 말한다.
  • 즉 클라이언트 입장에서 서브 타입은 슈퍼 타입의 한 종류여야 한다.

1. 서브 타입과 계약

  • 자식이 부모의 서브타입이 되려면 아래를 만족해야 한다.
    1. 서브 타입에 더 강력한 사전 조건을 정의할 수 없다. ( 서브에 슈퍼와 같거나 더 약한 사전 조건을 정의할 수 있다. )
    2. 서브 타입에 슈퍼와 같거나 더 강한 사후 조건을 정의할 수 있다.