Post

오브젝트: 서브클래싱과 서브타이핑의 차이와 리스코프 치환 원칙

상속은 두 가지 용도로 사용된다.

  1. 타입 계층을 구현
    • 타입 계층안에서 부모 클래스는 일반적인 개념, 자식 클래스는 특수한 개념을 구현한다.
    • 부모 클래스는 자식 클래스의 일반화(generalization)이고, 자식 클래스는 부모 클래스의 특수화(specialization)이다.
  2. 코드 재사용
    • 재사용을 위해 상속을 사용하게 되면 부모와 자식클래스 사이에 결합도가 높아져 변경이 어려워 진다.

상속은 코드 재사용이 아닌 타입 계층을 구현하는 목표로 사용해야 다형적으로 동작하는 객체들의 관계에 기반해 확장 가능하고 유연한 설계를 얻는 장점을 얻을 수 있다.

1. 타입

개념 관점의 타입

  • 인식하는 객체들에 적용하는 개념이나 아이디어를 가리켜 타입이라 한다.
  • 어떤 대상이 타입으로 분류될 때 그 대상을 타입의 인스턴스라고 한다.
    • 일반적으로 타입의 인스턴스를 객체라 한다.

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

  • 비트 묶음에 의미를 부여하기 위해 정의된 제약과 규칙을 가리킨다.
  • 적용 가능한 오퍼레이션의 종류와 의미를 정의함으로써 코드의 이미를 명확하게 전달하고, 개발자의 실수를 방지하기 위해 사용한다.

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

  • 객체가 수신할 수 있는 메시지의 종류를 정의하는 것을 뜻한다
    • 클래스의 퍼블릭 인터페이스라고 생각하면 된다
  • 동일한 퍼블릭 인터페이스를 제공하는 객체들은 동일한 타입이다.

2. 타입 계층

타입 사이의 포함관계

  • 타입은 객체들의 집합이기 때문에 다른 타입을 포함 하는 것이 가능하다.
  • 타입 계층을 구성하는 두 타입 중 더 일반적인 타입을 슈퍼타입, 더 특수한 타입을 서브 타입이라고 부른다.

내연과 외연 관점에서 서브타입과 슈퍼타입은 다음과 같이 정의할 수 있다.

  • 슈퍼타입
    • 집합이 다른 집합의 모든 멤버를 포함한다
    • 타입 정의가 다른 타입보다 좀 더 일반적이다
  • 서브타입
    • 집합에 포함되는 인스턴스들이 더 큰 집합에 포함된다
    • 타입 정의가 다른 타입보다 좀 더 구체적이다.

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

  • 더 일반적인 퍼블릭 인터페이스를 가지는 객체들은 더 특수한 퍼블릭 인터페이스를 가지는 객체들의 슈퍼타입이다.
  • 서브 타입의 인스턴스는 슈퍼타입의 인스턴스로 간주될 수 있다.

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

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

아래 두 질문에 모두 만족할 때 사용해야 한다.

  • 상속 관계가 is-a 관계를 모델링하는가?
    • 일반적으로 자식 클래스는 부모 클래스다 라고 말해도 이상하지 않다면 상속을 사용할 후보로 간주 할 수 있다. 후보다 후보.
  • (더 중요)클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가?
    • 클라이언트 입장에서는 부모와 자식 클래스의 차이점을 몰라야 한다. 이를 두 클래스 사이의 행동 호환성이라 부른다.

is-a 관계

  • 어떤 타입 S가 다른 타입 T의 일종이라면 타입 T라고 말할 수 있어야 한다.

행동 호환성(중요)

  • 타입 이름 사이에 개념적인 연관성이 있더라도 행동에 연관성이 없다면 is-a 관계를 사용하면 안된다.
  • 두 타입 사이에 행동이 호환되는 경우에만 타입 계층으로 묶어야 한다.
  • 호환여부를 판단하는 기준은 클라이언트의 관점이다. 클라이언트가 두 타입이 동일하게 행동할 것일고 기대한다면 두 타입을 타입 계층으로 묶을 수 있다.

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

  • 클라이언트에 따라 인터페이스를 분리하면 변경에 대한 영향을 더 세밀하게 제어할 수 있다.
  • 인터페이스를 클라이언트의 기대에 따라 분리함으로써 변경에 의해 영향을 제어하는 설계 원칙을 인터페이스 분리 원칙(ISP)이라 한다.

서브 클래싱과 서브타이핑

상속을 사용하는 두 가지 목적에 대한 특별한 이름인 서브 클래싱과 서브 타이핑

  • 서브 클래싱
    • 다른 클래스의 코드를 재사용할 목적으로 상속하는 경우를 뜻함
    • 자식 클래스와 부모 클래스의 행동이 호환되지 않아 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 없다
    • 구현 상속 또는 클래스 상속이라고 부른다
  • 서브 타이핑
    • 타입 계층을 구성하기 위해 상속을 사용하는 경우를 뜻함
    • 자식 클래스와 부모 클래스의 행동이 호환되기 때문에 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 있음
      • 부모 클래스는 자식 클래스의 슈퍼타입, 자식 클래스는 부모 클래스의 서브타입
    • 인터페이스 상속이라고 부른다

서브 타이핑 관계가 유지되기 위해서는 서브 타입이 슈퍼타입이 하는 모든 행동을 동일하게 할 수 있어야 한다. 즉, 어떤 타입이 다른 타입의 서브 타입이 되기 위해서는 행동 호환성이 만족되어야 한다.

4. 리스코프 치환 원칙

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

클라이언트와 대체 가능성

(중요) 대체 가능성을 결정하는 것은 클라이언트이다.

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

  • 리스코프 치환 원칙은 클라이언트가 어떤 자식 클래스와도 안정적으로 협력할 수 있는 상속 구조를 구현할 수 있는 가이드라인을 제공한다
  • 확장성도 높다
    • 개방 폐쇄 원칙을 지원한다
      • 클라이언트 관점에서 자식 클래스가 부모 클래스를 대체할 수 있다면 기능 확장을 위해 자식 클래스를 추가하더라도 코드를 수정할 필요가 없어진다. 즉, 리스코프 치한 원칙은 개방 폐쇄 원칙을 만족하는 설계를 위한 전제조건이다.
      • 일반적으로 리스코프 치한 원칙이 위반되면 잠재적인 개방 폐쇄 원칙 위반이다.

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

  • 계약에 의한 설계
    • 클라이언트와 서버 사이의 협력을 의무와 이익으로 구성된 계약의 관점에서 표현
  • 계약에 의한 설계 구성 요소 3가지
    • 사전 조건
      • 클라이언트가 정상적으로 메서드를 실행하기 위해 만족시켜야 하는 조건
    • 사후 조건
      • 메서드가 실행된 후에 서버가 클라이언트에게 보장해야하는 조건
    • 클래스 불변식
      • 메서드 실행 전과 실행 후 인스턴스가 만족시켜야 하는 불변식

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

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

서브 타입과 계약

  • 서브 타입에 더 강력한 사전 조건을 정의 할 수 없다.
    • 부모 클래스에서는 사전 조건을 통과시켜 메시지를 전달했지만 자식 클래스에서 허용하지 않기 때문에 협력에 실패한다.
  • 서브타입에 슈퍼타입과 같거나 더 약한 사전조건을 정의할 수 있다.
    • 부모 타입에서 이미 사전 조건을 확인했기 때문에 별 다른 영향은 없다.
  • 서브타입에 슈퍼타입과 같거나 더 강한 사후조건을 정의할 수 있다.
    • 부모는 0원 이상을 반환 받기를 기대할 때, 자식은 천원 이상일 때만 메시지를 전달 할 수 있다.
  • 서브타입에 더 약한 사후 조건을 정의할 수 없다
    • 부모는 0원 이상을 반환 받기를 기대하지만 더 약한 사후조건으로 인해 자식 클래스에서는 마이너스를 반환할 경우 협력에 실패한다.
This post is licensed under CC BY 4.0 by the author.