38: "has-a(...는...를 가짐)" 혹은 "is-implemented-in-terms-of(...는...를 써서 구현됨)"를 모형화할 때는 객체 합성을 사용하자
합성(composition)이란, 어떤 타입의 객체들이 그와 다른 타입의 객체들을 포함하고 있을 경우에 성립하는 그 타입들 사이의 관계를 일컫는다. 포함된 객체들을 모아서 이들을 포함한 다른 객체를 합성한다는 뜻인데, 이를 테면 다음과 같은 경우다.
class Address{};
class PhoneNumber{};
class Person {
public:
private:
std::string name; // 이 클래스를 이루는 객체들
Address address;
PhoneNumber voiceNumber;
PhoneNumber faxNumber;
};
객체 합성에는 두가지 뜻이 있다. 하나는 "has-a(...는...를 가짐)"이고 하나는 "is-implemented-in-terms-of(...는...를 써서 구현됨)"이다. 이렇게 뜻이 두 개인 이유는 소프트웨어 개발에서 우리가 대하는 영역(domain)이 두 가지이기 때문이다.
일상생활에서 볼 수 있는 사물을 본 뜬 사람, 이동수단 등의 이런 객체는 소프트웨어의 응용 영역(Application domain)에 속한다. 응용 영역에 속하지 않는 나머지들은 버퍼, 뮤텍스 등은 순수하게 시스템 구현만을 위한 인공물이다. 이런 객체가 속한 부분은 소프트웨어의 구현 영역(implementation domain)이다. 여기서 객체 합성이 응용 영역의 객체들 사이에서 일어나면 has-a 관계이다. 반면, 구현 영역에서 일어나면 is-implementated-in-terms-of 관계이다.
is-a 관계와 is-implementated-in-terms-of 관계의 차이점이 헷갈릴 것이다. 위의 Person 클래스가 나타내는 관계는 has-a 관계이다. 사람이 주소를 가진다고 말하는 것이 자연스럽다. 사람은 주소를 써서 구현된다라고 말할 수는 없다.
한 가지 예를 들어보자. 저장공간을 적게 차지하는 집합(set)을 구현해야 하는 상황이다. 표준 라이브러리의 set 템플릿은 균형 탐색 트리(balanced search tree)로 구현되어 있어, 원소 한 개당 포인터 세 개의 오버헤드가 걸린다. 탐색, 삽입, 삭제의 시간 복잡도를 로그 시간으로 보장하기 위해 이렇게 만들어져 있다. 그러나 지금 상황은 속력보다는 공간이기에 새로운 템플릿을 만들어야 한다. C++ 라이브러리의 연결 리스트(linked list)인 list 템플릿을 사용하기로 결정했다. Set 템플릿을 list에서 파생된 평태로 만든다고 하자. 다시 말해, Set<T>는 list<T>를 상속받는다. Set 객체는 list의 일종이 된 것이다.
template<typename T>
class Set : public list<T> {};
is-a 관계가 성립하면 기본 클래스에서 참인 것들이 파생 클래스에서도 참이어야 한다. 그러나 list 객체는 중복 원소를 가질 수 있다. 따라서 Set이 list의 일종이라는 명제는 참이 아니다.
is-a 관계는 현재 맞지 않다. Set 객체는 list 객체를 써서 구현되는(is-implemented-in-terms-of) 형태의 설계가 가능하다.
template<typename T>
class Set {
public:
bool member(const T& item) const;
void insert(const T& item);
void remove(const T& item);
private:
std::list<T> rep; // Set 데이터의 내부 표현부
};
이것만은 잊지 말자!
- 객체 합성(composition)의 의미는 public 상속이 가진 의미와 완전히 다르다.
- 응용 영역에서 객체 합성의 의미는 has-a(...는...를 가짐)이다. 구현 영역에서는 is-implemented-in-terms-of(...는...를 써서 구현됨)의 의미를 갖는다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] 40: 다중 상속은 심사숙고해서 사용하자 (2) | 2023.11.09 |
---|---|
[Effective C++] 39: private 상속은 심사숙고해서 구사하자 (0) | 2023.11.07 |
[Effective C++] 37: 어떤 함수에 대해서도 상속받은 기본 매개변수 값은 절대로 재정의하지 말자 (0) | 2023.11.05 |
[Effective C++] 36: 상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물! (0) | 2023.11.05 |
[Effective C++] 35: 가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자 (1) | 2023.11.04 |