로또
38. has-a(...는 ...를 가짐) 혹은 is-implemented-in-terms-of(...는 ...를 써서 구현됨)를 모형화할 때는 객체 합성을 사용하자 본문
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 의미를 가진다고 하였다.
객체 합성은 다음과 같이 두 가지 의미를 가진다.
- has-a (...는 ...를 가짐)
- is-implemented-in-terms-of (...는 ...를 써서 구현됨)
뜻이 두 가지인 이유는 SW 개발에서 우리가 대하는 영역(domain)이 두 가지이기 때문이다.
- Application domain (응용 영역) - 일상생활의 사물을 본뜬 것. 사람, 이동수단, 비디오 프레임 등
- 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(...는 ...를 써서 구현됨)의 의미를 가진다.
'책 > Effective C++' 카테고리의 다른 글
| 40. 다중 상속은 심사숙고해서 사용하자. (0) | 2023.12.09 |
|---|---|
| 39. private 상속은 심사숙고해서 구사하자. (1) | 2023.12.08 |
| 37. 어떤 함수에 대해서도 상속받은 기본 매개변수 값은 절대로 재정의하지 말자. (0) | 2023.12.06 |
| 36. 상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물! (1) | 2023.12.05 |
| 35. 가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자. (0) | 2023.12.04 |