상속보다는 조합

Jaca·2021년 12월 5일
0

상속

새로은 클래스는 기존 클래스에 기반을 둔 클래스 상속을 사용하여 정의할 수 있다.
서브클래스(sub class)는 부모 클래스(parent class)를 상속하면 부모 클래스가 갖는 모든 데이터와 연산을 서브클래스가 갖게 된다.

클래스 상속 대 인터페이스 상속

일단 클래스와 타입의 차이를 이해해야 한다.

  • 객체의 클래스 : 그 객체가 어떻게 구현되느냐를 정의한다.
    클래스는 객체의 내부 상태와 그 객체의 연산에 대한 구현 방법을 정의한다.
  • 객체의 타입 : 그 객체의 인터페이스, 즉 그 객체가 응답할 수 있는 요청의 집합을 정의한다.

클래스도 객체가 수행할 수 있는 연산을 정의 하므로, 객체의 타입을 정의하는 것이기도 한다.
그래서 어떤 객체가 어떤 클래스의 인스턴스라고 말할 때, 그 객체는 그 클래스가 정의한 인터페이스를 지원한다는 뜻이기도 하다.

  • 클래스 상속 : 객체의 구현을 정의할 때 이미 정의된 객체의 구현을 바탕으로 한다.
    코드와 내부 표현 구조를 공유하는 메커니즘이다.
  • 인터페이스 상속(서브타이핑) : 어떤 객체가 다른 객체 대신에 사용될 수 있는 경우를 지정하는 매커니즘이다.
    인터페이스 상속 관계가 있다면 프로그램에는 슈퍼타입으로 정의하지만 런타임에 서브타입의 객체로 대체할 수 있다. (OCP, LSP)

클래스 상속은 기본적으로 부모 클래스에서 정의한 구현을 재사용하여 응용프로그램의 기능성을 확장하려는 메커니즘이다.
이미 있는 것을 이용해서 새로운 객체를 빨리 정의해 보려는 것 위함으로, 기존의 클래스를 그대로 상속할 수 있다면 새로운 구현에 드는 비용은 공짜인 셈.

추상 클래스를 정의하고 인터페이스 개념으로 객체를 다룰 때 얻을 수 있는 점은,

  1. 사용자가 원하는 인터페이스를 그 객체가 만족하고 있는 한, 사용자는 그들이 사용하는 특정 객체 타입에 대해 알아야 할 필요는 없다.
  2. 사용자는 이 객체들을 구현하는 클래스를 알 필요가 없고, 단지 인터페이스를 정의하는 추상 클래스가 무엇인지만 알면 된다.

상속의 문제는?

객체지향 시스템에서 기능의 재사용을 위해 구사하는 가장 대표적인 기법은 클래스 상속, 그리고 객체 조합(object composition) 이다.

우리가 상속을 통해 얻고자 하는 점은,

  1. 코드를 재사용함으로써 중복을 줄일 수 있다.
  2. 변화에 대한 유연성 및 확장성이 증가한다.
  3. 개발 시간이 단축된다.

하지만, 상속을 적절하게 사용하지 못하면 아래와 같은 단점이 있다.

캡슐화를 깨뜨린다.

캡슐화는 말그대로 캡슐에 넣듯 외부에서 감춘다는 의미이다.

캡슐화: 만일의 상황(타인이 외부에서 조작)에 대비해 외부에서 특정 속성이나 메서드를 사용할 수 없도록 숨겨놓는 것.

클래스 상속은 서브 클래싱, 즉 다른 부모 클래스에서 상속받아 한 클래스의 구현을 정의하는 것이다.
서브클래싱에 의한 재사용을 화이트박스 재사용(white-box reuse)이라고 합니다. 상속을 받으면 부모 클래스의 내부가 서브클래스에 공개되기 때문에 화이트박스인 셈이다.

화이트박스 재사용은 캡슐화가 깨짐으로써 하위 클래스가 상위 클래스에 강하게 결합, 의존하게 되고 강한 결합, 의존은 변화에 유연하게 대처하기 어려워진다.

조합은 이러한 클래스 상속에 대한 대안이다.
다른 객체를 여러개 붙여서 새로운 기능 혹은 객체를 구성함으로써,
객체의 내부는 공개되지 않고 인터페이스를 통해서만 재사용하고 이를 블랙박스 재사용(black-box reuse) 라고 한다.

  • 클래스 상속의 장점 - 컴파일 시점에 정적으로 정의되고 프로그래밍 언어가 직접 지원하므로 그대로 사용하면 된다.
    클래스 상속으로 부모 클래스의 구현을 쉽게 수정할 수도 있는데, 서브클래스는 모든 연산이 아닌 일부만 재정의할 수도 있다.
  • 클래스 상속의 단점 - 런타임에 상속받은 부모 클래스의 구현을 변경할 수는 없다.
    왜냐하면 상속은 컴파일 시점에 결정되기 때문이다.
    서브클래스는 부모 클래스의 구현에 종속될 수밖에 없으므로 부모 클래스 구현에 변경이 생기면 서브클래스도 변경해야 합니다.

이러한 단점을 해결하기 위해서 추상 클래스를 이용할 수도 있다.
추상 클래스에는 구현이 거의 없거나 전혀 없기때문에 이미 추상 클래스를 상속했다는 것은 구현이 아닌 인터페이스를 상속한 것이므로 구현 자체는 서브클래스가 정의한다.
구현이 변경되면 서브클래스만 변경하면 되고 상위 추상 클래스는 고려할 필요가 없습니다.

조합

조합(Composition): 기존 클래스가 새로운 클래스의 구성요소로 쓰인다.
새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조한다.

하지만 조합은 한 객체가 다른 객체에 대한 참조자를 얻는 방식으로 런타임에 동적으로 정의된다.

객체가 다른 객체의 인터페이스만 바라보게 하기 때문에 인터페이스 정의에 더 많은 주의를 기울여야 하지만 객체는 인터페이스에서만 접근하므로 캡슐화를 유지할 수 있다.

동일한 타입을 갖는다면 다른 객체로 런타임에 대체가 가능하므로 객체는 인터페이스에 맞춰 구현되므로 구현 사이의 종속성은 확실히 줄어든다.

상속보다 조합을 사용하는 이유는
각 클래스의 캡슐화를 유지할 수 있고,
각 클래스의 한 가지 작업에 집중할 수 있다.
클래스와 클래스 계층이 소규모로 유지되면서 유지 보수가 간결하다.
객체 합성으로 서계되면 클래스의 수는 적어지고 객체의 수는 좀 더 많아질 수 있지만, 시스템의 행동은 클래스에 정의된 정적인 내용보다는 런타임에 드러나는 객체 합성의 의한 상호 관련성에 따라 달라질 수 있다.

더 나은 재사용

재사용을 위해서 새로운 구성요소를 생성할 필요 없이 필요한 기존의 구성요소를 조립해서 모든 새로운 기능을 얻어올 수 있다.
그러나 가능한 구성요소의 집합이 실제로 사용할 수 있을 만큼 충분하지 않기 때문에 기존 구성요소의 조합을 통한 재사용만으로 목적을 달성할 수 있는 경우는 적다.
상속에 의한 재사용은 기존 클래스들을 조합해서 새로운 구성요소를 쉽게 만들 수 있도록 해 준다.
그러므로 상속과 조합을 적절히 사용해야 완벽한 재사용이 가능하다.

결론

캡슐화를 깨뜨리고, 상위 클래스에 의존하게 돼서 변화에 유연하지 못한 상속을 사용하기보다는 조합(Composition)을 사용하자.

하지만 조합(Composition)이 상속보다 무조건 좋다는 것은 아니다.
상속이 적절하게 사용되면 조합보다 강력하고, 개발하기도 편리하다.

profile
I am me

0개의 댓글