본문 바로가기

IT Book Summary/Object: 객체지향설계

Chapter 13 서브클래싱과 서브타이핑

상속의 두가지 용도

- 타입계층을 구현하는 것 : 부모클래스는 자식클래스의 일반화(generalization), 자식클래스는 부모클래스의 특수화(specialization)

- 코드 재사용 : 점진적으로 어플리케이션을 확장. 부모 자식이 강하게 결합.

 

동일한 메시지에 대해 다르게 행동할 수 있는 다형적인 객체를 구현하기 위해

객체의 행동은 기반으로  타입계층을 구성

 

객체지향 프로그래밍(Object-Based Programming)과 객체기반 프로그래밍(Object-Oriented Programming)

 

객체지향 프로그래밍은 상속과 다형성을 지원, 객체기반 프로그래밍은 지원하지 않음.

종종 객체기반이 자바스크립트처럼 프로토타입 기반 언어(Prototype-Based Language) 를 사용한

클래스 없이 객체만을 사용하는 방식을 가리키기도 함.

 


01 타입

 

 

개념 관점의 타입

어떤 대상이 타입으로 분류될 때 그 대상을 타입의 인스턴스라고 함.

일반적으로 객체.

 

  • 심볼(Symbol) : 타입에 이름을 붙인것
  • 내연(intension) : 타입의 정의. 공통적 속성이나 행동
  • 외연(extension) : 타입에 속하는 객체들의 집합.

 

프로그래밍 관점의 타입

 

비트 묶음에 의미를 부여하기 위해 정의된 제약과 규칙

 

두가지 목적

  • 타입에 수행될 수 있는 유효한 오퍼레이션의 집합을 정의 - 객체 타입에 따라 적용가능한 종류를 제한
  • 타입에 수행되는 오퍼레이션에 대해 미리 약속된 문맥을 제공 - 객체가 문맥을 결정

 

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

 

  • 개념관점에서 타입이란 공통의 특징을 공유하는 대산들의 분류
  • 프로그래밍 언어 관점에서 타입이란 동일한 오퍼레이션을 적용할 수 있는 인스턴스들의 집합

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

 

동일한 인터페이스를 제공하는 객체들은 동일한 타입으로 분류됨.

 

객체가 외부에 제공하는 행동에 의해 타입이 결정됨.


02 타입 계층

 

 

타입 사이의 포함관계

 

타입은 집합의 관점에서 더 세분화된 타입의 집합을 부분집합으로 포함 가능하다.

 

때문에 동일한 인스턴스가 하나 이상의 타입으로 분류되는 것도 가능

 

타입간에 일반화와 특수화 관계가 존재

 

더 일반적인 타입을 - 슈퍼타입 supertype , 더 특수한 타입을 - 서브타입 subtype

 

일반화는 다른 타입을 완전히 포함하거나 내포하는 타입을 식별하는 행위 또는 그 행위의 결과를 가리킴

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

 

슈퍼타입

  • 집합이 다른 집합의 모든 멤버를 포함한다.
  • 타입 정의가 다른 타입보다 좀 더 일반적이다.

서브타입

  • 집합에 포함되는 인스턴스들이 더 큰 집합에 포함된다
  • 타입 정의가 다른 타입보다 좀 더 구체적이다.

 

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

 

퍼블릭 인터페이스의 관점에서

 

슈퍼타입이란 : 서브타입이 정의한 퍼블릭 인터페이스를 일반화시켜 상대적으로 범용적이고 넓은 의미로 정의한 것.

 

서브타입이란 : 슈퍼타입이 정의한 퍼블릭 인터페이스를 특수화시켜 상대적으로 구체적으로 좁은 의미로 정의한 것.

 

 


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

 

타입을 구현하는 일반적인 방법, 클래스를 이용. 타입 계층을 구현하는 일반적인 방법, 상속을 이용

 

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

 

마틴 오더스키의 두가지 질문

- 상속 관계가 is-a 관계를 모델링하는가?

- 클라이언트 입장에서 부모클래스의 타입으로 자식 클래스를 사용해도 무방한가? (부모 자식사이 행동 호환성)

 

is-a 관계

 

어휘적 정의가 아닌 기대되는 행동에 따라 타입계층의 의미가 달라질 수 있음.

슈퍼타입과 서브타입 관계에서는 행동 호환성이 중요

 

 

행동 호환성

 

두 타입 사이에 행동이 호환될 경우에만 타입계층으로 묶어야 한다.

동일한 메서드를 구현하면 행동을 호환하는것인가?

판단하는 기준은 클라이언트의 관점이다.

타입계층이 사용될 문맥을 이해하는 것이 중요하다.

 

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

 

날수 있는 새와 날수 없는 새 분리, 행동을 기반으로 한 인터페이스 생성

날수없는 새에게 fly메시지를 던질 수 없게 만듬.

인터페이스를 클라이언트가 기대하는 것에 따라 분리 -> 인터페이스 분리 원칙(Interface Segregation Principle, ISP)

 

더 좋은 방법을 합성을 사용하는 것.

 

두 클래스 사이 행동이 호환되지 않으면 올바른 타입계층이 아니므로 상속을 쓰면 안됨.

 

서브클래싱과 서브타이핑

 

상속을 사용하는 목적의 의해서 나뉨

  • 서브클래싱(subclassing) - 다른 클래스 코드 재사용을 목적으로 함. 행동이 호환되지 않으므로 자신이 부모의 인스턴스를 대체할 수 없다. == 구현상속 implementation inheritance 클래스상속 class inheritance

 

  • 서브타이핑(subtyping) - 타입 계층을 구성하기 위한 목적. 행동이 호환되기 때문에 자식 클래스의 인스턴스가 부모클래스 인스턴스를 대체할 수 있다. == 인터페이스 상속 Interface inheritance 

행동 호환성과 대체가능성은 상속관계를 위한 지침이다 - 리스코프 치환원칙으로 정리

 

 

 


04 리스코프 치환 원칙

 

S형의 각 객체 o1에 대해 T형의 객체 o2가 하나 있고,

T에 의해 정의된 모든 프로그램 P에서 T가 S로 치환될 때,

P의 동작이 변하지 않으면 S는 T의 서브타입이다.

 

ex) 직사각형은 사각형이다. Square is-a Rectangle

 

resize 관점에서 Rectangle 대신 Square를 사용할 수 없음. 단지 구현을 재사용하고 있을뿐.

 

클라이언트와 대체 가능성

 

클라이언트 관점에서 Square와 Rectangle 이 다름.

 

리스코프 치환원칙은 자식클래스가 부모클래스를 대체하기 위해서는

부모클래스에 대한 클라이언트의 가정을 준수해야 한다는 것을 강조함.

 

대체 가능성을 결정하는 것은 클라이언트

 

is-a 관계 다시 살펴보기

 

마틴 오더스키의 두가지 질문.

 

슈퍼타입과 서브타입이 클라이언트 입장에서 행동이 호환되면 두타입을 ia-a로 연결해 문장을 만들어도

어색하지 않은 단어로 타입의 이름을 정하라.

 

상속이 서브타이핑을 위해 사용될 경우에만 is-a관계. 역시 행동이 우선

 

 

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

 

유연하고 확장성 또한 높다.

 

퍼블릭 인터페이스의 행동이 변경되지 않으면 클라이언트의 코드를 변경하지 않고도 새로운 자식클래스와 협력 가능.

 

ex) DiscountPolicy 와 새로운 자식클래스인 OverlappedDiscountPolicy

 

  • 의존성 역전 원칙 : 구체클래스가 모두 추상 클래스에 의존. 상위수준 모듈과 하위수준 모듈이 모두 추상클래스에 의존
  • 리스코프 치환 원칙 : 클라이언트에 대한 영향 없이 대체 가능
  • 개방-폐쇄 원칙 : 새로운 기능을 추가하기 위해 자식클래스를 추가하더라도 영향을 안끼침. 기능확장을 하면서 기존 코드를 수정하지 않음.

 

 

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

 

클래스 상속을 타입계층을 구현할 수 있는 다양한 방법 중 하나. 다른 여러 기법이 있음.

 

but 리스코프 치환원칙을 준수해야함. 

 

객체 지향 구현 단위로써 클래스를 사용하고 코드 재사용의 목적으로

클래스상속을 지나치게 남용해 리스코프 치환원칙을 위반하는 경우가 많음.

 


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

 

 

계약에 의한 설계

- 클라이언트가 정상적으로 메서드를 실행하기 위해 만족시켜야 하는 사전조건 precondition

- 메서드가 실행된 후에 서버가 클라이언트에게 보장해야 하는 사후조건 postcondition

- 메서드 실행 전과 실행 후에 인스턴스가 만족시켜야 하는 클래스 불변식 class invariant

 

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

 

클라이언트의 입장에서 서브타입은 슈퍼타입의 한 종류여야 함

 

서브클래스가 서브타입이 아닐수 있음. 상속을 받지만 부모클래스를 대체할 수 없다면.

 

서브타입과 계약

 

계약의 관점에서 상속이 초래하는 큰 문제는 자식클래스가 부모클래스의 메서드를 오버라이딩 할 수 있다는 것.

 

자식클래스가 부모클래스의 서브타입이 되기 위해서는 다음을 만족시켜야 함.

 

- 서브타입에 더 강력한 사전조건을 정의할 수 없다.

 

- 서브타입에 슈퍼타입과 같거나 더 약한 사전조건을 정의할 수 있다.

 

- 서브타입에 슈퍼타입과 같거나 더 강한 사후조건을 정의할 수 있다.

 

- 서브타입에 더 약한 사후조건을 정의할 수 없다.