오브젝트 chapter13

서브클래싱과 서브타이핑

  1. 상속의 첫 번째 용도: 타입 계층을 구현
    • 타입 계층 안에서 부모 클래스는 일반적인 개념을 구현하고 자식 클래스는 특수한 개념을 구현한다.

    • 타입 계층의 관점

      • 부모 클래스는 자식 클래스의 일반화(generalization)
      • 자식 클래스는 부모 클래스의 특수화(specialization)
  2. 상속의 두 번째 용도: 코드 재사용
    • 점진적으로 애플리케이션의 기능을 확장 가능
    • 결합이 강해지기 때문에 변경하기 어려운 코드가 될 확률이 높음

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

결론: 동일한 메시지에 대해 서로 다르게 행동할 수 있는 다형적인 객체를 구현하기 위해서는 객체의 행동을 기반으로 타입 계층을 구성해야 한다.

1 타입

개념 관점의 타입

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

  • 심볼 : 타입에 이름 붙인 것
  • intension : 타입의 정의로서 타입에 속하는 객체들이 가지는 공통적인 속성이나 행동
  • extension : 타입에 속하는 객체들의 집합

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

연속적인 비트에 의미와 제약을 부여하기 위해 사용됨.

  1. 타입에 수행될 수 있는 유효한 오퍼레이션의 집합을 정의
  2. 타입에 수행되는 오퍼레이션에 대해 미리 약속된 문맥을 제공
    • 객체를 생성하는 방법에 대한 문맥을 결정하는 것이 객체의 타입이다.

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

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

객체가 수신할 수 있는 메시지의 종류를 정의하는 것. 객체가 수신할 수 있는 메시지의 집합은 퍼블릭 인터페이스다.

객체지향 프로그래밍에서 타입을 정의하는 것은 객체의 퍼블릭 인터페이스를 정의하는 것과 동일.

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

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

2 타입 계층

타입 사이의 포함관계

타입 계층을 구성하는 두 타입 간의 관계에서 더 일반적인 타입을 슈퍼타입이라고 부르고 더 특수한 타입을 서브타입이라고 부른다.

일반화는 다른 타입을 완전히 포함하거나 내포하는 타입을 식별하는 행위 또는 그 행위의 결과를 가리킨다. 특수화는 다른 타입 안에 전체적으로 포함되거나 완전히 내포되는 타입을 식별하는 행위 또는 그 행위의 결과를 가리킨다.

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

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

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

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

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

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

두 질문에 해당하는 경우만 상속을 사용하라

  1. 상속 관계가 is-a 관계를 모델링하는가?
    • 자식 클래스는 부모클래스다
  2. 클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가?
    • 상속 계층을 사용하는 클라이언트의 입장에서 부모 클래스와 자식 클래스의 차이점을 몰라야 함
      • 자식 클래스와 부모 클래스 사이의 행동 호환성이라 부름

is-a 관계

기대되는 행동에 따라 타입 계층을 구성해야 한다. 타입 계층의 의미는 행동이라는 문맥에 따라 달라질 수 있다.

행동 호환성

행동의 호환 여부를 판단하는 기준은 클라이언트의 관점이다.

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

행동 호환성을 만족시키지 않는 상속 계층을 그대로 유지한 채 클라이언트의 기대를 충족시킬 수 있는 방법을 찾기란 쉽지 않다. 문제를 해결할 수 있는 방법은 클라이언트의 기대에 맞게 상속 계층을 분리하는 것뿐이다.

클라이언트에 따라 인터페이스를 분리하면 변경에 대한 영향을 더 세밀하게 제어할 수 있게 된다. 대부분의 경우 인터페이스는 클라이언트의 요구가 바뀜에 따라 변경된다. 인터페이스를 클라이언트의 기대에 따라 분리함으로써 변경에 의해 영향을 제어하는 설계 원칙을 인터페이스 분리 원칙(Interface Segregation Principle, ISP)이라고 부른다.

중요한 것은 설계가 반영할 도메인의 요구사항이고 그 안에서 클라이언트가 객체에게 요구하는 행동이다.

서브클래싱과 서브타이핑

  • 서브클래싱: 재사용 목적으로 상속을 사용하는 경우. 구현 상속 또는 클래스 상속이라 부름
  • 서브타이핑: 타입 계층을 구성하기 위해 사용. 인터페이스 상속이라고 부름.

서브클래싱과 서브타이핑을 나누는 기준은 상속을 사용하는 목적이다.

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

행동 호환성을 만족하는 상속 관계는 부모 클래스를 새로운 자식 클래스로 대체하더라도 시스템이 문제없이 동작할 것이라는 것을 보장해야 한다. 자식 클래스와 부모 클래스 사이의 행동 호환성은 부모 클래스에 대한 자식 클래스의 대체 가능성을 포함한다.

4 리스코프 치환 원칙

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

행동이 호환될 경우에만 자식 클래스가 부모 클래스 대신 사용될 수 있다.

클라이언트와 대체 가능성

리스코프 치환 원칙은 “클라이언트와 격리한 채로 본 모델은 의미 있게 검증하는 것이 불가능하다”는 아주 중요한 결론을 이끈다. 어떤 모델의 유효성은 클라이언트의 관점에서만 검증 가능하다.

상속 관계는 클라이언트의 관점에서 자식 클래스가 부모 클래스를 대체할 수 있을 때만 올바르다. 대체 가능성을 결정하는 것은 클라이언트다.

is-a 관계 다시 살펴보기

is-a 관계는 객체지향에서 중요한 것은 객체의 속성이 아니라 객체의 행동이라는 점을 강조한다.

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

새로운 자식 클래스를 추가하더라도 클라이언트의 입장에서 동일하게 행동하기만 한다면 클라이언트를 수정하지 않고도 상속 계층을 확장할 수 있다. 클라이언트의 입장에서 퍼블릭 인터페이스의 행동 방식이 변경되지 않는다면 클라이언트의 코드를 변경하지 않고도 새로운 자식 클래스와 협력할 수 있게 된다.

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

클래스 상속은 타입 계층을 구현할 수 잇는 다양한 방법 중 하나일 뿐.

리스코프 치환 원칙을 위반하는 예를 설명하는 데 클래스 상속을 자주 사용하는 이유는 대부분의 객체지향 언어가 구현 단위로서 클래스를 사용하고 코드 재상용의 목적으로 상속을 지나치게 남용하는 경우가 많기 때문.

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

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

리스코프 치환 원칙과 계약에 의한 설계 사이의 관계

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

어떤 타입이 슈퍼타입에서 정의한 사전조건보다 더 약한 사전조건을 정의하고 있다면 그 타입은 서브타입이 될 수 있지만 더 강한 사전조건을 정의한다면 서브타입이 될 수 없다. 어떤 타입이 슈퍼타입에서 정의한 사후조건보다 더 강한 사후조건을 정의하더라도 그 타입은 여전히 서브타입이지만 더 약한 사후조건을 정의한다면 서브타입의 조건이 깨지고 만다.

results matching ""

    No results matching ""