Computer Science/C & C++

[C++] Types of Relationship | 클래스간 관계의 유형 (IS-A, HAS-A 등)

lww7438 2020. 3. 5. 23:46

- 다수의 클래스를 정의할 때, 공통되는 특성들만을 모아놓은 하나의 클래스를 설계하고, 그 그 클래스에 대한 "IS-A" 또는 "HAS-A" 관계를 밝혀서 나머지 클래스를 구현할 때, 상속을 통해 구현할 지, 멤버로 포함시켜 구현할지를 정한다.
- 아래 4가지의 관계를 구현하기 위해 \(\texttt{public}\) Inheritance(\(\texttt{public}\) 상속)을 이용할 수는 있지만, 프로그래밍 문제가 발생할 여지가 있으므로, 가급적 \(\texttt{public}\) 상속은 "IS-A" 관계를 구현하는 수단으로만 사용하자. 

IS-A Relationship (IS-A 관계)

- "IS-A"는 "~는 a이다."라는 표현으로, "주어에 A가 포함된다."를 의미한다.
- "IS-A" 관계가 성립한다는 것은, 기초 클래스의 기능의 전부를 파생 클래스도 수행할 수 있는 상태를 의미한다.
(IS-A 관계는 대칭과는 거리가 멀다.)
- \(\texttt{public}\) 상속에서처럼, Derived Class(파생 클래스) 객체가 Base Class(기초 클래스) 객체이기도 할 때 두 클래스의 관계를 "IS-A" Relationship("IS-A" 관계)라고 한다.
- IS-A 관계에서는 기초 클래스의 구현내용과 인터페이스가 모두 파생 클래스에 전달된다.
- "is-a-kind-of" 표현이 더 정확하겠지만, 흔히 "IS-A" 표현을 더 많이 사용한다.
- 명시적 데이터형 변환 과정없이, 기초 클래스의 포인터/참조가 파생 클래스 객체를 지시/참조할 수 있는 것이 IS-A 관계의 특성이다. (단, 파생 클래스 객체가 추가적으로 갖고있는 데이터 멤버는 무시된다.)
- 반대로, 파생 클래스의 포인터/참조가 기초 클래스 객체를 지시/참조할 수 있기 위해서는, DownCasting 과정이 필요하며, 무조건 성공한다는 보장도 없다.

※ 파생 클래스 객체에 기초 클래스 객체를 대입하는 방법

// 1. 파생 클래스에만 있는 부가적인 데이터 멤버들에 대한 디폴트 값을 갖고있는 변환 생성자를 정의한다.
Son(const Father& ft, S_Member_1 s1 = value_1, S_Member_2 s2 = value_2);
// Son(const Father& ft)와 같은 생성자를 변환 생성자라 칭한다.


// 2. 기초 클래스를 파생 클래스에 대입하기 위한 대입 연산자 함수를 정의한다.
Son& Son::operator=(const Father& ft);


// 3. 명시적 데이터형 변환 (이 경우에는 DownCasting)

HAS-A Relationship (HAS-A 관계)

- "HAS-A"는 "~는 A를 가진다."라는 표현으로, "주어가 A를 소유한다."를 의미한다.
- "HAS-A" 관계가 성립한다는 것은, 두 클래스가 상속 관계가 아닌, 한 클래스가 다른 클래스를 멤버로 갖고있는 관계를 의미한다.
- HAS-A 관계에서는 인터페이스는 모른채 구현 내용만 얻게 된다. (IS-A 관계에서는 구현 내용과 인터페이스 둘 다 얻는다.)
- HAS-A 관계에 있는 클래스에서 포함된 클래스의 메서드를 이용하려면 \(\texttt{::}\) 연산자를 통해, 포함된 클래스 이름을 명시해야 한다.
ex) 사용자 정의 클래스 \(\texttt{Stock}\) 내부에 \(\texttt{string}\) 객체의 메서드를 사용하려면, \(\texttt{string::operator<()}\) 표현을 사용하는 \(\texttt{Stock::operator<()}\) 메서드를 정의하면 된다.
- 일반적으로, Containment(컨테인먼트)*, \(\texttt{private}\) Inheritance(\(\texttt{private}\) 상속), \(\texttt{protected}\) Inheritance(\(\texttt{protected}\) 상속)은 HAS-A 관계를 나타내는 수단으로 사용된다.

* Containment = Composition = Layering, 이 세 단어는 모두 같은 의미이며, 다른 클래스 객체를 클래스 멤버로 사용하는 기법을 의미한다.

HAS-A 구현 수단으로써의 Containment vs \(\texttt{private}\) Inheritance

- 일반적으로, HAS-A 관계를 모델링하려면 컨테인먼트를 사용해야 하며, 새 클래스가 오리지널 클래스의 \(\texttt{protected}\) 멤버에 접근해야 하거나, 가상 함수를 재정의할 필요가 있는 경우에만 \(\texttt{private}\) 상속을 사용하도록 한다.

Containment (컨테인먼트)
- 대부분의 프로그래머들이 선호하는 방식으로, 포함시킨 객체를 그 객체의 이름을 통해 활용할 수 있기 때문에 사용하기 쉽다.
- 같은 클래스의 종속 객체를 두 개 이상 포함시킬 수도 있다. (상속과 대비적인 점)
- 상속에 비해, 문제를 일으킬 소지가 적다. (다중 상속 문제 등등)
- 상속과 관련된 개념들을 일체 사용할 수 없다. (\(\texttt{private}\)이나 \(\texttt{protected}\) 멤버에 대한 접근, \(\texttt{virtual}\) 함수 재정의 등)

\(\texttt{private}\) Inheritance (\(\texttt{private}\) 상속)
- 위에 명시한 바와 같은 상속의 고질적인 단점들이 존재한다.
- 가상 함수 기능을 이용할 수 있다.

 


Is-Implemented-As-A Relationship (Is-Implemented-As-A 관계)

- "Is-Implemented-As-A"는 "~는 A를 통해 구현된다."라는 표현으로, "주어가 A를 통해 구현된다."를 의미한다.
- "Is-Implemented-As-A"관계가 성립한다는 것은, A가 B를 통해 구현되기는 하지만, A가 B에 속하는 개념은 아님을 의미한다.
ex) \(\texttt{stack}\)은 \(\texttt{array}\)를 통해 구현될 수는 있지만, \(\texttt{array}\) 로부터 \(\texttt{stack}\)을 파생시키지는 않는다. 스택은 배열에는 없는 특성이 있기 때문이다. 즉, 스택은 배열을 구현의 수단으로써 사용하지만, 배열에 포함되는 개념은 아님을 의미한다.


USES-A Relationship (USES-A 관계)

- "USES-A"는 "~가 A를 이용한다."라는 표현으로, "주어가 A를 이용하지만, A를 포함시키지는 않는다."를 의미한다.
- 클래스가 객체를 이용하긴 하지만, 객체의 값을 저장하지는 않는 관계를 의미한다.