6장. 상속, 그리고 객체 지향 설계🕶

안녕하세요! 두두코딩 입니다 ✋
오늘은 Effective C++ 항목 38에 대해 알아보겠습니다.

🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!

궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇

합성이란?

C++에서 사용하는 합성이란 하나의 객체에서 다른 타입의 객체들을 모은 결과를 의미한다. 예를 들어 Person을 구성하는 Class를 보도록 하자.

class Address { ... };

class PhoneNumber { ... };

class Person {
	public:
		...
	private:
		// 해당 클래스를 이루는 구성 중 하나
		std::string name;
		Address address;
		PhoneNumber voiceNumber;
		PhoneNumber faxNumber;
};

위의 예시와 같이, Person 객체는 string, Address, PhoneNumber과 같이 다양한 객체를 갖고 만들어 졌다. 해당 케이스를 C++에서는 합성이라고 부른다. 프로그래밍에서는 해당 합성을 레이어링, 포함, 통합 또는 내장 이라고도 부른다.

우리는 public 상속 같은 경우도 “is-a(..는 ..의 일종이다)”라고 언급한 적이 있다. 객체 합성 역시 항목 38 제목과 같이, 의미가 있는데 2가지 의미로 사용된다.

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

합성이 2가지의 의미로 사용되는 이유는 domain (영역)에 대해 다르게 사용되기 때문이다.

domain 별 영역으로 구별되는 합성의 의미

객체는 domain 별로 영역을 나눠서 생각해 볼 수 있다.

우리 일생생활을 볼 수 있는 사물을 본 뜬 것들이 있는 객체 (e.g. 사람, 이동수단, 비디오 프레임 등)를 소프트웨어의 응용영역 이라고 부른다.

응용영역을 제외한 나머지들 (e.g. 버퍼, 뮤텏, 탐색트리 등) 즉, 순수하게 시스템 구현만을 위한 인공물 객체를 소프트웨어의 구현영역 이라고 부른다.

위 2가지 영역 별로 합성의 의미가 구별된다.

  1. 소프트웨어의 응용영역의 합성을 has-a (…는 ..를 가짐)
  2. 소프트웨어의 구현영역의 합성을 is-implemented-in-terms-of (..는 ..를 써서 구현됨)

우리가 초반에 예시로 보여줬던 Person 클래스 같은 경우도 소프트웨어의 응용영역으로 “has-a” 관계를 가진다.

“has-a” 관계 같은 경우 우리가 일상적으로 사용하는 부분들이라서 “합성”의 의미가 잘 와닿는다. 반면에 “is-implemented-in-terms-of” 같은 경우 “합성”의 의미가 잘 와닿지 않는데, 해당 부분을 좀 더 자세하게 알아보자.

구현영역의 합성

시스템 설계의 관점에서 구현영역의 합성이 왜 필요한지에 대해 생각해보자.

우리는 set함수의 성능향상을 위해 list 컨테이너를 활용해 새롭게 설계를 하고자한다. 이 경우에 list를 “is-a” 관계와 같이 public 상속을 받아서 사용할 것인가? 아니면 구현영역의 “is-implemented-in-terms-of” 합성을 활용해 사용할 것인가?

is-a 관계 구현

template<typename T>
class Set : public std::list<T> { ... };

위 코드와 같이 public 상속을 받아 구현한다면, list 컨테이너의 요소를 다 쓸 수 있어서 편리하다고 생각할 수 있다. 하지만, list를 상속받을 경우 list 속성을 다 물려받아야하는데 list 같은 경우 중복 원소를 가질 수 있는 컨테이너이다.

하지만, set 컨테이너 같은 경우 중복 원소가 없도록 구현되어져야 하기 때문에 설계 자체에서 오류가 발생한다.

즉, 해당 기법이 아닌 다른 설계를 채택해야한다.

is-implemented-in-terms-of 관계 구현

우리가 구현해야 되는 Set 함수 같은 경우 public 상속 관계가 아닌 합성 관계로 표시해야되는 것을 알 수 있다.

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() const;
	private:
		// Set 데이터 내부 표현 부분을 담당.
		// 합성으로 구축 됨.
		std::list<T> rep;
};

// 멤버함수는 list를 활용해서 구현됨
...

void Set<T>::remove(const T& item)
{
	typename std::list<T>::iterator it =
		std::find(rep.begin(), rep.end(), item);

	if (it != rep.end()) rep.erase(it);
}

...

위의 설계와 같이, 자료구조 설계와 같은 시스템을 위한 설계를 할 때 합성을 사용해야 되는 경우가 있다. 이럴 경우 우리는 public 상속 방법이 아닌 is-implemented-in-terms-of 관계로 설정해서 설계를 해야한다.