Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
관리 메뉴

로또

38. has-a(...는 ...를 가짐) 혹은 is-implemented-in-terms-of(...는 ...를 써서 구현됨)를 모형화할 때는 객체 합성을 사용하자 본문

책/Effective C++

38. has-a(...는 ...를 가짐) 혹은 is-implemented-in-terms-of(...는 ...를 써서 구현됨)를 모형화할 때는 객체 합성을 사용하자

아롱로또 2023. 12. 7. 12:09

다루는 내용

객체 합성에 대해 알아보고 public 상속(is-a 관계)과 비교해보자.

합성(Composition)

 

어떤 타입의 객체들이 그와 다른 타입의 객체들을 포함하고 있을 경우에 성립하는 그 타입들 사이의 관계를 일컫는다. 포함된 객체들을 모아 이들을 포함한 다른 객체를 합성한다는 의미로 다음과 같은 경우를 말한다.

class Address{...};
class PhoneNumber{...};
class Person{
private:
	string name;
	Address address;
	PhoneNumber phone_number;
};

위 코드에서 Person 객체는 string, Address, PhoneNumber 객체로 이루어져 있다.

합성(Composition) 외에도 레이어링(Layering), 포함(Containment), 통합(Aggregation), 내장(Embedding) 등으로 불린다.

 

객체 합성의 의미

이전에 public 상속은 is-a 의미를 가진다고 하였다.

객체 합성은 다음과 같이 두 가지 의미를 가진다.

  1. has-a (...는 ...를 가짐)
  2. is-implemented-in-terms-of (...는 ...를 써서 구현됨)

뜻이 두 가지인 이유는 SW 개발에서 우리가 대하는 영역(domain)이 두 가지이기 때문이다.

  1. Application domain (응용 영역) - 일상생활의 사물을 본뜬 것. 사람, 이동수단, 비디오 프레임 등
  2. Implementation domain (구현 영역) - 시스템 구현을 위한 인공물. 버퍼, 뮤텍스, 탐색 트리 등

객체 합성이 응용 영역에서 일어나면 has-a 관계이고, 구현 영역에서 일어나면 is-implemented-in-terms-of 관계이다.

 

is-a vs is-implemented-in-terms-of

상대적으로 많이 헷갈리는 두 관계를 설명하기 위해 예시를 든다.

 

중복을 허용하지 않고 저장 공간을 적게 차지하는 클래스를 제작하려고 한다.

set 템플릿을 사용하고자 했지만 set은 균형 탐색 트리로 구성되어 한 개의 원소 당 포인터 세 개의 오버헤드가 발생한다. 이번에는 시간복잡도 보다는 공간복잡도를 중요시 하기 때문에 연결 리스트(Linked List)를 이용하여 공간을 보다 적게 차지하게 만들어보려고 한다.

 

is-a

// list를 public 상속하여 Set 구현
template<typename T>
class Set: public std::list<T>{...};

Set<T>는 list<T>를 상속받아서 Set 객체는 list 객체의 일종이 되었다.

하지만 Set<T>는 중복 원소를 허용하지 않아야 하는데, list<T>는 중복 원소를 허용한다.

즉, Set이 list의 일종(is-a)이라는 명제는 거짓이다. 따라서 public 상속은 원하는 관계를 모형화하는데 옳지 않다.

is-implemented-in-terms-of

// list를 내부 변수로 가져 Set 구현
template<typename T>
class Set{
public:
	bool Member(const T& item) const;
	void Insert(const T& item);
	void Remove(const T& item);
	std::size_t Size() const;
	...
private:
	std::list<T> rep; // Set 데이터의 내부 표현부
};

Set 객체를 list 객체를 써서 구현되는(is implemented in terms of) 형태의 설계가 가능하다. 때문에 list를 Set의 멤버 변수로 두어 원하는 기능을 list를 이용하여 다음과 같이 간단하게 구현할 수 있다.

template<typename T>
bool Set<T>::Member(const T& item) const
{
	return std::find(rep.begin(), rep.end(), item) != rep.end();
}

template<typename T>
void Set<T>::Insert(const T& item)
{
	if(!Member(item)) rep.push_back(item);
}

template<typename T>
void Set<T>::Remove(const T& item)
{
	typename std::list<T>::iterator iter = std::find(rep.begin(), rep.end(), item);
	if(iter != rep.end()) rep.erase(iter);
}

template<typename T>
std::size_t Set<T>::Size() const
{
	return rep.size();
}

 

이것만은 잊지 말자!

  • 객체 합성(Composition)의 의미는 public 상속이 가진 의미와 완전히 다르다.
  • 응용 영역에서 객체 합성의 의미는 has-a(...는 ...를 가짐)이다. 구현 영역에서는 is-implemented-in-terms-of(...는 ...를 써서 구현됨)의 의미를 가진다.