프로그래밍 공부
작성일
2024. 1. 29. 20:34
작성자
WDmil
728x90

5.4 객체 간 관계

프로그래밍을 하다 보면 서로 다른 두 클래스가 공통적인 속성을 가지거나 서로 어떤 연관 관계가 있는 경우를 만나게 된다. 예를 들어 그림, 음악, 보관 목록의 문자를 모두 나타내는 미디어 객체가 너무 일반적이긴 하지만 이들 객체가 뭔가 공통적인 속성이 있는 것 만큼은 분명하다. 예를 들어 세 미디어 모두 최종 수정 날짜를 가질 수도 있고, 삭제하기 행동을 가질 수도 있다.

 

객체지향 언어는 객체 간 관계에 대응하기 위한 몇 가지 매커니즘을 지원한다. 여기서 어려운 부분은 실제로 그 관계가 무엇인가? 하는 부분으로 크게 has-a 관계와 is-a관계 두가지가 있다.


5.4.1 Has-A관계

A가 B를 가질 때 또는 A가 B를 포함할 때 A와 B는 has-a관계 또는 집합(aggregation)관계를 가졌다고 말한다. 이런 종류의 관계는 어떤 객체가 다른 객체의 부분이 되는 것으로, 앞서 보았던 컴포넌트도 다른 객체가 구성요소로 컴포넌트를 가지는 has-a 관계다.

 

실세계 예로 동물원과 원숭이 관계를 들 수 있다. '동물원이 원숭이를 가졌다' 또는 '동물원은 원숭이를 포함한다' 고 말할 수 있다. 동물원 시뮬레이션 프로그램은 동물원 객체 안에 멤버 컴포넌트로서 원숭이를 가질 것이다.

 

사용자 인터페이스 시나리오를 생각하면 객체 간 관계를 이해하는데 도움이 된다. 왜냐하면 거의 모든 UI 인터페이스는 객체지향 개념으로 구현되어서 화면에 나타나는 모든 것이 객체로 대응되기 때문이다. 예를 들어 창과 버튼은 has-a관계를 가진다.

 

창과 버튼은 분명히 서로 다른 객체이다. 하지만 당연하게도 서로 연계되는 어떤 관계가 있다. 버튼은 창 안에 있기 때문에 창이 버튼을 가졌다고 할 수 있다.

 

has-A관계

 


5.4.2 Is-A 관계(상속관계)

is-a관계는 '무엇은 무엇의 한 종류다' 라는 뜻으로 파생, 서브클래싱, 확장, 상속 등 여러 이름으로 불리며 객체지향 프로그램의 근간이 된다. 클래스 모델은 실 세계의 것들이 프로퍼티와 행동으로 이루어져 있다는 데 기반하고, 상속 모델은 이런 객체들이 어떤 계층을 이루며 조직화되는 경향이 있다는 사실에 기반한다. 이러한 계층은 is-a관계, 즉 무엇은 무엇의 한 종류에 속한다 는 관계 구조를 표현한다.

 

기본적으로 상속 관계는 'A는 B의 한 종류다' 또는 'A는 B의 상당 부분을 닮았다' 라는 패턴을 따른다. 이러한 고나계는 종종 애매할 수 있다. 간단한 경우부터 보기 위해 동물원 예를 생각해보자. 이번에는 원숭이 말고 다른 동물도 있다. 바로 이문장 자체도 이미 관계를 형성했다. 원숭이는 동물의 한 종류이다. 비슷하게 기린도 동물이고 캥거루도 동물이고 펭귄도 동물이다. 상속의 마법은 원숭이, 기린, 캥거루, 펭귄 사이에 어떤 공통점이 있다는 것을 깨닫는 데서 온다.

 

프로그래머에게 이것이 의미하는 바는 Animal 클래스를 통해 모든 동물이 가진 프로퍼티 (크기, 위치, 행동) 와 행동(이동, 먹기, 잠자기) 을 하나로 감쌀[encapsulate]*(은닉화 라고도 함)수 있다는 점이다. 예를들어 원숭이는 Animal 클래스의 파생 클래스로 정의되어 동물이 가지는 모든 특징을 포함할 수 있다. 원숭이는 동물의 특성에 원숭이만의 특성이 더해졌다는 사실을 상기하자. 

 

is-a관계

원숭이와 기린이 서로 다른 동물이듯이 그래픽 사용자 인터페이스 의 버튼들도 서로 다를 수 있다. 예를들어 체크박스도 버튼의 한 종류다. 클릭하면 뭔가 행동을 취하는 UI 컴포넌트를 버튼이라고 생각해보자. CheckBox는 Button클래스를 확장하여 상태값 프로퍼티를 추가함으로써 체크와 체크 해제를 표시할 수 있는 박스가 된다.

 

클래스 간에 is-a관계를 맺을 떄는 공통의 기능을 상위 클래스에 묶어 넣어 다른 클래스로 확장할 수 있게 하는 것이 목적이다.

 

만약 클래스들이 서로 비슷한 또는 완전히 같은 코드를 가지고 있다면 해당 코드를 상위 클래스로 묶어내는 것을 고려하는 것이 좋다. 그렇게 함으로써 공통부분에 코드 수정이 발생했을 때 미래에 생성될 클래스까지 포함하여 모든 하위 클래스에 자동으로 적용할 수 있다.


5.4.2.1 클래스 상속 요령

앞서 살펴본 예제들에서는 정규적인 정리 없이 클래스 상속을 사용했다. 상속을 통해 부모 클래스( 또는 베이스 클래스 또는 슈퍼 클래스) 에서 파생 클래스를 만들 떄는 몇 가지 특징적인 방식이 있다. 파생 클래스는 이러한 방식들을 조합하여 만들어질 수 있는데, 'A는 ~하고 ~하는 B다' 와 같은 문장을 정의하는 과정과 같다.


기능 추가

부모 클래스에 기능이 추가되어 파생 클래스가 만들어질 수 있다. 예를 들어 원숭이는 나무를 건너뛸 수 있는 동물이다. Monkey 클래스는 Animal이라는 클래스의 모든 행동을 가지면서 추가로 원숭이만의 특징적 요소인 '나무 건너뛰기' 라는 행동을 가진다.


기능 변경

부모 클래스의 행동을 완전히 바꾸는(오버라이드) 방식으로 파생 클래스가 만들어질 수 있다. 예를 들어 대부분의 동물은 이동할 때 다리를 이용해서 걷는다. 따라서 Animal클래스의 move메서드를 구현할 때 걷기 방식을 적용했을 것 이다. 그런데 캥거루는 이동할 때 걷지 않고 뜀뛰기를 한다. Kangaroo 클래스는 Animal클래스의 다른 행동들은 모두 동일하게 적용받지만 이동에 대해서는 다른 방식이 적요오딘다. 물론 베이스 클래스의 모든 행동을 바꿀 수도 있다. 하지만 베이스 클래스가 추상 클래스가 아니라면 그런 상황이 발생한다는 것 자체가 애초에 파생 클래스를 만드는 것이 잘못된 디자인임을 암시한다. 추상 클래스는 직접 인스턴스를 생성할 수 없는 클래스로, 파생 클래스들이 각기 특정 행동을 구현한다.


프로퍼티 추가

베이스 클래스에 새로운 프로퍼티가 추가되어 파생클래스가 만들어질 수 있다. 예를 들어 펭귄은 동물의 모든 프로퍼티를 가지되 추가적으로 '부리 크기' 라는 프로퍼티가 있을 수 있다.


프로퍼티 변경

C++는 베이스 클래스의 행동을 바꾸듯이, 베이스 클래스의 프로퍼티를 바꾸는 방식으로 파생 클래스를 만들 수 있게 해준다. 하지만 그런 경우가 합리적인 상황은 매우 드물다. 왜냐하면 베이스 클래스로부터 그 속성은 은닉되기 때문이다.

 

파생 클래스에서 프로퍼티를 바꾸는 것과 파생 클래스의 프로퍼티 값이 다른 것은 완전히 다른 개념이라는 점에 주의해야 한다. 예를 들어 모든 동물은 무엇을 먹느냐에 따라 먹이라는 프로퍼티를 가질 수 있다. 이때 파생 클래스인 원숭이와 펭귄은 각각 바나나와 물고기를 먹는다. 두 동물의 먹이 프로퍼티가 가진 값은 다르지만 먹이 프로퍼티 항목 자체가 다르진 않다. 단지 프로퍼티에 할당된 값이 다를 뿐이다.


5.4.2.2 코드 재사용과 다형성

다형성(polymorphism)은 표준적인 프로퍼티와 행동의 집합을 정의해두고, 그것을 따르는 객체라면 그중 어느 객체를 이용하든 정상적으로 이용할 수 있다는 개념이다. 클래스 정의는 객체와 그것을 이용하는 코드 간 계약과도 같아서, 예를 들어 Monkey객체는 그 정의에 의해 Monkey클래스의 프로퍼티와 행동을 지원해야 한다.

 

이러한 개념은 베이스 클래스에도 마찬가지로 적용된다. 모든 원숭이가 동물이듯이 모든 money객체는 Animal클래스의 프로퍼티와 행동을 지원해야 한다.

 

다형성은 객체지향 프로그래밍의 가장 아름다운 부분으로 상속 개념의 장점을 제대로 이용하고 있다. 동물원 시뮬레이션 예제에서는 동물원의 모든 동물을 프로그램적으로 순회하면서 각 동물을 한 번씩 이동(move)하게 할 수 있다. 모든 동물은 Animal 클래스의 멤버 이기 떄문에 이동이라는 행동을 할 수 있다. 그런데 어떤 동물은 다른 이동 방식으로 행동을 바꿔서(오버라이딩) 적용했을 수도 있다. 바로 이것이 중요한 부분으로, 단지 각 동물에게 이동하라는 명령을 내릴 뿐 각 동물이 실제로 어떤 방식으로 이동하는지 전혀 알 필요가 없다. 각각의 동물은 스스로 자신이 어떻게 이동해야 하는지 알고 있고 그에 따라 행동한다.

 

다형성의 활용 말고도 단순히 기존 코드를 재활용하기 위한 목적으로 파생 클래스를 이용하기도 한다. 예를 들어 에코 음향 효과를 가진 뮤직 플레이 클래스를 만들어야 할 때 동료가 이미 만들어놓은 음향 효과가 없는 베이직 뮤직 클래스가 있다면, 그 클래스를 가져와서 음향 효과 기능을 추가하여 파생 클래스를 만들 수 있다.

 

이때 '음향 효과가 있는 뮤직 플레이 클레스' 로서 is-a관계가 적용되기는 하지만 의도한 것은 아니다. 베이스 클래스와 파생 클래스의 객체를 어디서든 서로 호환되게, 즉 다형성을 활용하려는 목적이 아니라 단지 코드 재사용을 목적으로 하고 있다. 이 경우 파생 클래스는 상속관계와는 관계없이 완전 별개의 부품으로 활용되며, 상속 관계는 바퀴를 두 번 발명하는 것을 피하기 위한 목적으로만 쓰인다.


5.4.3 Has-A관계와 Is-A관계의 얇은 경계

실세계에서는 객체간 소유 관계(has-a)와 분류 관계(is-a)를 구분하는 것이 어렵지 않다. 누구도 오렌지가 과일을 가졌다고 생각하지 않는다. 오렌지는 과일의 한종류라고 말한다. 하지만 코드에서는 이러한 관계가 분명치 않은 경우가 있다.

 

예를 들어 해시(hash)테이블 클래스를 생각해보자. 해시 테이블은 키와 값을 효과적으로 매핑하기 위한 데이터 구조로 되어있다. 어떤 보험사는 hastable클래스를 이용해서 멤버 ID와 그에 따른 멤버 이름을 매핑하여 ID로 이름으 루십게 찾을 수 있게 한다. 이때 멤버 ID는 키가 되고 멤버 이름은 값이 된다.

 

표준적인 해시 테이블 구현에서는 키 하나에 한개의 값이 매핑된다. 만약 ID가 14534가 '클래퍼, 스콧'이라는 멤버 이름에 매핑되었다면, 같은 ID가 '클래퍼, 마르니'에는 매핑될 수 없다. 대부분의 해시 테이블에서 이미 매핑된 키에 중복해서 ㄱ밧을 매핑하면 앞서 매핑된 값을 덮어써버린다. 다시말해, ID 14534에 '클래퍼, 스콧' 이 매핑된 상태에서 또다시 DI 14534에 '클래퍼 , 마르니' 를 매핑하면 클래퍼 스콧의 보험 등록 정보가 사라져 버린다.

키 하나에 복수의 값을 매핑할 수 있는 해시 테이블 도 있을 수 있다. 보험사는 같은 ID에 한가족을 모두 등록하여 여러 개의 이름이 매핑되게 하고 싶을 수 있다.

 

데이터 구조는 기존 해시 테이블과 거의 같으나 여러개의 이름이 매핑될 수 있도록 기능을 확장해야 한다. 이를 위해 문자열 대신 배열이나 리스트 같은 컬렉션을 이용해서 복수의 문자열을 담는다. 그리고 이미 패밍된 정보가 존재하는 ID에 이름을 입력하면 기존 이름을 삭제하고 다시 쓰는 대신 컬랙션에 이름을 추가한다.

 

문자열 대신 컬렉션을 이용하다 보니 코드가 늘어나고 중복된 부분도 많다. 컬렉션 이용 부분을 감싸서 여러 값을 한 키에 매핑할 수 있는 별도의 클래스를 만드는 것이 바람직해 보인다. 그러면 클래스를 MultiHash라고 하자, MultiHash클래스는 HashTable과 같은 방식으로 작동하지만, 값 저장용 변수로 문자열 대신 문자열의 컬렉션을 이용하는 것이 다르다. 분명 MultiHash는 HashTable과 연관이 있다. 데이터 저장에 해시 테이블을 쓰는 것은 동일하기 때문이다. 하지만 한 가지 분명하지 않은것이 있다. 두 클래스가 is-a관계인지 아니면 has-a관계인지 여부다.

 

먼저 is-a관계로 시작해보자. MultiHash가 hashTable의 파생 클래스가 되어 행동을 오버라이드 한다. 매핑을 입력하는 행동은 컬렉션을 생성하거나 이미 생성된 컬렉션에 새로운 항목을 추가하는 행동으로 오버라이딩 된다. 값을 찾는 행동도 오버라이딩하여 키에 해당하는 값을 모두 모아 한 문자열로 리턴하게 한다. 이러한 디자인은 해시 테이블의 모든 행동을 오버라이딩 함에도 불구하고 굉장히 자연스러워 보인다. 그리고 내부적으로는 여전히 베이스 클래스의 원래 행동에 의존하게 된다.

이제는 has-a관계로 접근해보자. MultiHash는 독립적인 클래스로, 내부에 Hashtable객체를 포함한다. 인터페이스는 Hashtable과 매우 유사하나 똑같지는 않다. 내부적으로는 사용자가 해시에 항목을 추가할 떄 hashTable에 컬렉션을 추가하는 작업을 감싸고 있다. 이러한 디자인도 전혀 이상하지 않고 합리적으로 보인다.

그러면 어떤 접근 방법이 올바른 것일까? 병확한 답은 없다. 단, MultiHash클래스를 상용 제품을 위해 개발했었던 프로그래머는 ahs-a관계가 올바르다고 보고 있다. 가장 큰 이유는 노출된 인터페이스를 hashtable의 유지보수에 대한 걱정 없이 수정할 수 있기 떄문이다.

 

예를들어 MultiHash에서는 get이 getALL로 변경되어 특정 키에 매핑된 모든 값이 리턴됨을 명시적으로 표현한다. 또한 has-a관계에서는 해시 테이블의 기능이 오용되는 상황도 막을 수 있다. 예를 들어 해시 테이블이 모든 값의 개수를 알려주는 기능을 지원한다면, is-a관계에서는 값의 개수가 아니라 컬렉션의 개수를 리턴해버려서 의도와 다른 행동을 하게된다. 하지만 has-a관계에서는 해당 행동을 MultiHash에서 명시적으로 지원해야 하므로 의도와는 다른 상황이 발생할 수 없다.

 

다른 누군가는 MultiHash가 hashTable의 기능 확장이 분명함으로 is-a관계가 맞다고 말할 수 있다. 프로그래밍을 하다보면 이렇게 has-a인지 is-a인지 관계를 판단하기 어려운 때가 있다. 그럴 때는 목적하는 것이 단지 다른 클래스의 기능을 재활용하는 것인지 아니면 그 클래스를 수정해서 기능을 새롭게 확장하려는 것인지 잘 생각해보아야 한다.

 

  is-a관계 has-a관계
지지 이유 기본적으로 같은 종류의 추상화며 단지 속성이 다를 뿐이다.
Hastable과 거의 똑같은 행동을 지원한다.
Hastable이 지원하는 행동을 모두 따져보지 않고 자유롭게  MultiHash에 기능을 추가할 수 있다.
Hashtable이 아닌 다른 무엇으로 성격이 바뀌더라도 Hashtable의 인터페이스에 대한 고민 없이 독립적으로 MultiHash를 구현할 수 있다.
반대 이유 해시 테이블은 그 정의에 의해 키 하나당 값 하나여야 하므로 MultiHash는 해시테이블이라 하기 어렵다!
MultiHash는 HashTable의 핵심 행동 두가지 (get/set)을 모두 오버라이딩 한다. 이것은 뭔가 디자인이 잘못되었다는 암시다.
숙지하지 못한 또는 잘못된 프로퍼티나 행동이 Multihash를 통해 오용될 수 있다.
MultiHash는 새로운 행동을 이유로 바퀴를 두번 발명하는것과 같다.
HashTable의 프로퍼티나 행동중 유용한 것이 있을 수 있다.

 

위 논점중 is-a관계에 대한 이유는 매우 사실 강력한 것이다. 대부분, 만약 선택이 가능한 상황이라면 is-a관계보다 has-a관계를 선택할것을 권고한다.

 

C++표준 라이브러리에서는 unordered_map클래스를 지원한다. Hashtable이나, Unorderd_Multimap대신 unordered_map을 이용하면 MultiHash역할을 해준다.


5.4.4 Not-A관계

클래스간 관계를 고민할 때는 애초부터 관계 자체가 필요한지 부터 판단해야 한다. 객체지향 디자인에 몰입된 나머지 불필요한 클래스/파생 클래스 관계를 만드느라 시간을 허비해서는 안된다.

 

실세계 객체로서는 관계가 자명하지만, 코드로 옮겨오고 보니 상황이 달라질 때 큰 곤란에 부딛힌다. 객체지향적 계층을 만들 떄는 임의로 정한 것이 아닌 기능적인 관계에 대한 모델이 있어야 한다.

존재론적 관점이나 계층적으로는 의미가 있지만, 코드에서는 별 의미가 없는 관계이다.

불필요한 상속을 피하려면 디자인 초안을 먼저 만드는 것이 좋다. 모든 클래스와 파생 클래스에 대해 준비중인 프로퍼티와 행동을 정리하여 디자인 초안을 만든다. 이때 고유한 프로퍼티나 행동이 존재하지 않거나, 프로퍼티나 행동이 파생 클래스에 완전히 오버라이딩 되는 클래스가 있다면 디자인을 재검토 해야한다. 단, 그 클래스가 추상베이스라면 예외다.

 


5.4.5 클래스 계층

클래스 A가 클래스B의 베이스 클래스이듯이 클래스 B또한 클래스 C의 베이스 클래스가 될 수 있다. 객체지향 계층은 이렇듯 다단계의 관계를 모델링할 수 있다. 동물원 시뮬레이션 예에서 많은 동물이 Animal클래스의 파생클래스로 디자인될 수 있다.

각 파생 클래스를 구현하면서 유사한 부분을 많이 발견하게 되면 해당 부분을 공통의 부모 클래스에 집어넣는 것이 좋다. Lion클래스와 Panther클래스를 구현하다 보면 이동 방식과 먹이가 비슷함을 발견할 수 있다. 이것은 BigCat이라는 부모 클래스를 만드는 것이 바람직함을 암시한다. 더 나아가서 Animal클래스를 물에 사는 동물과 유대동물(주머니에 새끼를 넣어 키우는 동물) 로 나눌 수도 있다.

생물학자가 본다면 이러한 계층이 마음에 들지 않을 것이다. 왜냐하면 펭귄은 돌고래와 같은 종이 아니기 때문이다. 여기에는 중요한 시사점이 있다. 코드 상에서는 실세계의 객체간 관계와 객체 간 기능적 관계 사이에서 균형을 취해야 한다. 어떤 두 사물이 실세계에서는 매우 비슷하더라도 코드 상에서는 공통 기능이 없어서 not-a관계로 만들어야 할 수도 있다. 실세계의 관계에 따른다면 동물을 포유류와 어류로 나누기가 더 쉽겠지만 코드 상의 베이스 클래스에서는 공통으로 가져가야 할 요소가 별로 없을 수 있다.

 

또 다른 중요한 점은 계층 디자인은 하나의 정답만 있는 것이 아니라 여러 대안이 있을 수 있다는 것이다.

앞어 설명한 계층은 동물이 어떻게 움직이냐를 중점으로 고려하여 디자인되었다. 만약 동물의 먹이나 높이를 기준으로 디자인했다면, 전혀 다른 결과가 나왔을 것이다. 결론적으로 클래스가 어떤방식으로 이용될 것인가가 가장 중요하다.

즉, 필요에 맞춰서 객체의 계층이 디자인된다.

 

좋은 객체지향 계층 디자인은 다음 원칙에 따라 만들어진다.

  • 클래스의 의미 있는 기능적 관계에 따라 조직화한다.
  • 공통 부분을 베이스 클래스에 위치시킴으로써 코드를 재사용 할 수 있게 한다.
  • 부모가 추상 클래스일 경우를 제외하고, 베이스 클래스의 많은 부분을 오버라이딩 하는 파생 클래스의 도입은 피한다.

5.4.6 다중 상속

지금까지의 예제들은 단일 상속 사슬만 보여주었다. 다른말로 하면 어떤 파생 클래스는 단 하나의 베이스 클레스만 가졌다. 그런데 하나의 베이스 클래스만 허용되는 것은 아니다. 다중 상속을 통해 하나 이상의 베이스 클래스를 가질 수도 있다.

 

여전히 Animal클래스가 베이스 클래스지만, 동물의 크기에 따라 파생 클래스로 나뉘고 있다. 그리고 추가로 먹이에 따른 계층, 이동방식에 따른 계층이 별도로 존재한다. 각 동물은 이러한 세 가지 계층에서 각 특성에 맞는 클래스를 베이스로 상속받아 파생 클래스로 만들어진다.

 

사용자 인터페이스로 만들 때 사용자가 클릭할 수 있는 이미지가 있다고 하자. 이 객체는 버튼이면서 이미지이기 때문에 Button클래스와 Image클래스를 모두 상속받아 구현한다.

다중 상속은 특정 상황에서 매우 유용하다. 하지만 염두해두어야 하는 단점도 많다. 많은 프로그래머가 다중 상속을 좋아하지 않는다. C++는 명식적으로 다중 상속 관계를 지원하지만, Java와 같은 언어는 다중 상속을 배제한다.

 

단, 복수의 인터페이스(추상 베이스 클래스) 를 상속하는 것은 예외적으로 지원한다.

 

다중 상속이 꺼려지는 몇 가지 이유가 있다.

  1. 다중 상속 관계는 그림으로 나타내기 복잡하다.
    5-10 그림에서 보듯, 매우 단순한 계층도임에도 다중 상속에 따른 교차 선분들 떄문에 복잡해 보인다. 클래스 계층은 프로그래머로 하여금 코드간 관계를 이해하기 쉽도록 돕기 위한 목적도 있다. 그런데 다중상속에서는 서로 전혀 관계없는 클래스들을 부모로 가질 수 있어서 어느 클래스가 지금 작업중인 객체의 코드에 영향을 미치는지 파악하기가 쉽지 않다.
  2. 다중 상속은 간명할 수도 있는 클래스 구조를 망가뜨릴 수 있다.
    동물예제에서 다중 상속을 이용해서 계층을 디자인하니 베이스 클래스인 Animal의 의미가 줄얻르었다. 왜냐하면 동물을 규정하는 기준이 다른 계층으로 분리되었기 떄문이다. 5-10의 세가지 계층도만 봐서는 이것이 왜 간명한 계층 디자인을 망가뜨리는지 이해가 안 될 수도 있다. 하지만 뜀뛰기를 하는 동물이 모두 같은 먹이를 먹는다는 것을 알았다면 어떻게 될까? 계층이 분리되어 있기 때문에 이동 방식과 먹이가 같아도 추가적인 파생 클래스 없이는 베이스 클래스를 합칠 방법이 없다.
  3. 다중 상속 계층은 구현하기가 까다롭다.
    만약 두 베이스 클래스가 같은 행동을 다른 방식으로 구현했다면 어떻게 될까? 이 경우 같은 베이스 클래스를 가지는 두 파생 클래스를 베이스 클래스로 가질 수 있을까? 이런 상황이 발생할 가능성 떄문에 구현이 까다로워진다. 클래스 자체적으로 내제한 관계를 구조화하는 것은 누구에게나 어려운 일이다.

이러한 이유 때문에 다른언어에서는 다중 상속을 제거하였으며, 보통은 다중 상속을 사용하지 않아도 충분하다. 프로젝트 전체 디자인에 대한 권한이 있다면, 클래스 계층을 다시 고민해서 다중 상속을 사용하지 않을 방법을 얼마든지 찾아낼 수 있을 것이다.


5.4.7 첨가 클래스

첨가(mixin)클래스는 클래스 관계의 또 다른 형태다. C++에서는 첨가 클래스가 문법적으로 다중 상속과 같다. 하지만 그 의미는 매우 다르다. 첨가 클래스는 '이 클래스가 이것 외에 할 수 있는 또 다른 일은 무엇인가?'에 대한 질문에 '~도 할 수 있다.' 라는 형태로 답한다. 첨가 클래스는 is-a관계를 완전히 구현하지 않고도 일정 기능을 추가하는 방법이다. 첨가 클래스는 공유 관계로도 생각할 수 있다.

 

동물원 예에서 애완용으로 기를 수 있는 동물을 따로 표현하고 싶다고 하자. 즉, 동물원 관람객은 물리거나 다치지 않고 집에서 기를 수 있는 애완동물을 구분할 수 있다. '애완용으로 기를 수 있는(Pattable)' 동물에게 '애완동물 되기' 라는 행동을 부여하고 싶다고 하자. 이미 만들어진 디자인 계층 상에서는 애완동물이 가지는 공통적인 특징이 없다. 그렇다고 기존의 계층을 꺠트리면서 기능을 추가할 수 도 없다. 이떄 Pattable을 첨가 클래스로 만들어 활용한다.

 

첨가 클래스는 사용자 인터페이스 디자인에 자주 사용된다. PicktureButton클래스를 '이미지이자 버튼이다.' 라고 하는 대신 '이미지이면서 클릭 가능하다(Clickable)'라고 한다. PC데스크톱에 있는 폴더 아이콘은 이미지이면서 드래그 가능하다(Draggable). Clickable이나 Draggable은 소프트웨어 개발자가 만든 신용 형용사이다.

 

첨가 클래스와 베이스 클래스의 차이점은 생각하는 방법이 다르다는 것 뿐 임으로 코드상의 차이점이 그리 크지 않다.

일반적으로 첨가 클래스는 용도상 연관된 범위가 제한적이기 때문에 다중 상속보다 소화하기 쉽다. Pettable 첨가 클래스는 이미 존재하는 클래스에 '애완동물 되기' 행동 한가지만 추가한다. Clickable 첨가 클래스도 'mouse down'과 mouse up' 두 가지 행동만 추가한다. 그리고 첨가 클래스는 큰 계층 구조를 가지는 경우가 드물어서 다중 상속 에서처럼 클래스 간에 기능(행동) 이 서로 충돌할 가능성도 낮다.

728x90