서브클래싱과 서브타이핑

Posted by Songi on 2020-04-25

“OBJECTS 13장”


  • 상속 용도

    • 타입 계층 구현

      • 부모 클래스는 자식 클래스의 일반화

      • 자식 클래스는 부모 클래스의 특수화

    • 코드 재사용

상속을 사용하는 일차적인 목표는 코드 재사용이 아니라 타입 계층을 구현하는 것이어야 한다.

타입

개념 관점의 타입

  • 타입의 인스턴스
    • 객체
    • 자바, 루비, 자바스크립트, C는 프로그래밍 언어의 인스턴스
  • 요소
    • 심볼
      • 타입의 이름
      • 프로그래밍 언어
    • 내연
      • 타입의 정의
      • 타입에 속하는 객체들이 가지는 공통적인 속성이나 행동
    • 외연
      • 타입에 속하는 객체들의 집합
      • 프로그래밍 언어 타입의 경우 자바, 루비, 자바스크립트, C가 속한 집합

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

  • 타입에 수행될 수 있는 유효한 오퍼레이션의 집합을 정의한다.
    • ‘+’ 연산자 : 원시형 숫자 타입이나 문자열 타입의 객체에 사용 가능, 클래스의 인스턴스에 대해서는 사용 불가
  • 타입에 수행되는 오퍼레이션에 대해 미리 약속된 문맥을 제공한다.
    • a + b , a와 b의 타입이 int 라면 두수를 더함, String 이라면 하나의 문자열로 합침

타입은 적용 가능한 오퍼레이션의 종류와 의미를 정의함으로써 코드의 의미를 명확하게 전달하고 개발자의 실수를 방지하기 위해 사용된다.

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

  • 타입이란 ?
    • 개념 관점 : 공통의 특징을 공유하는 대상들의 분류
    • 프로그래밍 언어 관점 : 동일한 오퍼레이션을 적용할 수 있는 인스턴스들의 집합
    • 객체지향 패러다임 관점 : 객체가 수신할 수 있는 메시지의 종류를 정의
  • 퍼블릭 인터페이스 : 객체가 수신할 수 있는 메시지의 집합
  • 객체지향 프로그래밍에서 타입을 정의하는 것은 객체의 퍼블릭 인터페이스를 정의하는 것

객체의 퍼블릭 인터페이스가 객체의 타입을 결정한다. 따라서 동일한 퍼블릭 인터페이스를 제공하는 객체들은 동일한 타입으로 분류된다.

  • 객체의 타입을 결정하는 것은 내부의 속성이 아니라 객체가 외부에 제공하는 행동임

타입 계층

타입 사이의 포함 관계

  • 슈퍼타입
    • 집합이 다른 집합의 모든 멤버를 포함
    • 타입 정의 다른 타입 보다 좀 더 일반적
  • 서브타입
    • 집합에 포함되는 인스턴스들이 더 큰 집합에 포함
    • 타입 정의가 다른 타입보다 좀더 구체적
  • 내연 관점
    • 일반화 : 어떤 타입의 정의를 좀 더 보편적이고 추상적으로 만드는 과정
    • 특수화 : 어떤 타입의 정의를 좀 더 구체적이고 문맥 종속적으로 만드는 과정

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

  • 슈퍼 타입
    • 서브타입이 정의한 퍼블릭 인터페이스를 일반화시켜 상대적으로 범용적이고 넓은 의미로 정의
  • 서브 타입
    • 슈퍼 타입이 정의한 퍼블릭 인터페이스를 특수화시켜 상대적으로 구체적이고 좁은 의미로 정의

서브타입의 인스턴스는 슈퍼타입의 인스턴스로 간주될 수 있다.

서브클래싱과 서브타이핑

  • 객체지향 프로그래밍 언어에서 타입을 구현하는 일반적인 방법 : 클래스
  • 타입 계층을 구현하는 일반적인 방법 : 상속

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

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

is-a 관계

1
2
3
4
5
6
7
class Bird {
public void fly() {...}
}

class Penguin extends Bird {
...
}
1
2
- 펭귄은 새다.
- 새는 날 수 있다.
  • 정의가 아니라 기대되는 행동에 따라 타입 계층을 구성해야 함
  • 타입 계층의 의미는 행동이라는 문맥에 따라 달라짐

행동 호환성

타입의 이름 사이에 개념적으로 어떤 연관성이 있다고 하더라도 행동에 연관성이 없다면 is-a관계를 사용하지 말아야 한다.

  • 클라이언트의 관점
1
2
3
4
5
6
7
8
class Penguin extends Bird {
...
// 오버라이딩 하여 내부 구현 비워두기
// bird가 날 수 있다는 클라이언트의 기대 만족시키지 못함
// Penguin과 Bird 행동 호환되지 않음
@Override
public void fly(){}
}
1
2
3
4
5
6
7
8
9
10
11
class Penguin extends Bird {
...
// 오버라이딩 후 예외 던지기
// 인자 타입에 따라 성공과 실패가 갈림
// 클라이언트가 기대하지 않은 예외
// Penguin과 Bird 행동 호환되지 않음
@Override
public void fly(){
throw new UnsupportedOperationException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
class Penguin extends Bird {
...
// 인자로 전달된 모든 Bird Penguin의 인스턴스가 아닐 경우에만
// fly() 메시지를 전송한다.
// instanceof 처럼 객체 타입을 확인하는 코드는 새로운 타입 추가시마다 코드 수정을 요구
// 개방-폐쇄 원칙 위반
@Override
public void flyBird(Bird bird){
if(!(bird instanceof Penguin)){
bird.fly();
}
}
}

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

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

  • 설계가 꼭 현실 세계를 반영할 필요는 없음
  • 중요한 것은 설계가 반영할 도메인의 요구사항이고, 그 안에서 클라이언트가 객체에게 요구하는 행동
  • 최고의 설계는 제작하려는 소프트웨어 시스템이 기대하는 바에 따라 달라짐
  • 나는 새도 있고 날 수 없는 새도 있다는 사실은 어떤 세계를 만들려고 하느냐에 따라 고려 대상이 되기도, 안되기도 함

서브클래싱과 서브타이핑

  • 서브클래싱
    • 다른 클래스의 코드를 재사용할 목적으로 상속을 사용 하는 경우
    • 구현 상속
    • 클래스 상속
  • 서브타이핑
    • 타입 계층을 구성하기 위해 상속을 사용하는 경우
    • 인터페이스 상속
  • 서브클래싱과 서브타이핑을 나누는 기준은 상속을 사용하는 목적

인터페이스 상속 관계를 갖는 경우 프로그램에는 슈퍼타입으로 정의하지만 런타임에 서브타입의 객체로 대체할 수 있다. … 즉, 추상클래스를 상속한다는 것은 단순한 코드의 재사용을 위한 상속이 아니라 추상 클래스가 정의하고 있는 인터페이스를 상속하겠다는 의미인 것이다.

  • 서브타입이 슈퍼타입이 하는 모든 행동을 동일하게 할 수 있어야 함
  • 행동 호환성
  • 대체 가능성

리스코프 치환 원칙

서브타입은 그것의 기반 타입에 대해 대체 가능해야 한다.

  • 자식 클래스가 부모 클래스와 행동 호환성을 유지함으로써 부모 클래스를 대체할 수 있도록 구현된 상속 관계만을 서브 타이핑이라고 불러야 함

클라이언트와 대체 가능성

  • 클라이언트와 격리한 채로 본 모델은 의미있게 검증하는 것이 불가능
  • 대체 가능성을 결정하는 것은 클라이언트

is-a 관계 다시 살펴보기

  • 상속이 서브타이핑을 위해 사용될 경우에만 is-a 관계
  • 서브클래싱을 구현하기 위해 상속을 사용했다면 is-a 관계라고 말할 수 없음

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

  • 자식 클래스가 클라이언트의 관점에서 부모 클래스를 대체할 수 있다면, 기능 확장을 위해 자식 클래스를 추가하더라도 코드를 수정할 필요가 없음

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

  • 구현 방법과 무관하게 클라이언트의 관점에서 슈퍼타입에 대해 기대하는 모든 것이 서브타입에게도 적용되야 함

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

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

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

서브타입과 계약

  • 계약의 관점에서 상속이 초래하는 가장 큰 문제

    • 자식 클래스가 부모 클래스의 메서드를 오버라이딩 할 수 있다는 것
  • 어떤 타입 : 슈퍼타입

    • 더 약한 사전 조건 : 더 강한 사전 조건
      • 서브 타입 될 수 있음
    • 더 강한 사전 조건 : 더 약한 사전 조건
      • 서브 타입 될 수 없음
    • 더 강한 사후 조건 : 더 약한 사후 조건
      • 서브 타입 될 수 있음
    • 더 약한 사후 조건 : 더 강한 사후 조건
      • 서브 타입 될 수 없음