트레이트 – 11.다중 상속 모델

클래스는 동시에 여러 개의 트레이트를 조합할 수 있습니다. 클래스에 use 문을 사용하여 여러 개의 트레이트를 쉼표로 구분하여 나열하면 다중 트레이트(multiple trait)를 구현할 수 있습니다.

다중 상속 모델

다중 상속(multiple inheritance)의 경우, 상속 받은 여러 기반 클래스에 같은 이름의 멤버가 존재할 가능성과 하나의 클래스를 간접적으로 두 번 이상 상속 받을 가능성이 있는 등 클래스들의 상속 관계가 모호해질 수 있습니다.

다중 상속에서의 다이어몬드 상속 관계

박쥐(bat)는 포유류(mammalia)와 조류(birds)의 속성을 모두 가지고 있습니다. 포유류와 조류는 Animal로부터 상속받았기 때문에 전형적인 다이아몬드 타입의 다중 상속 모델이 나타나게 되며, 이로 인해 포유류와 조류의 메소드가 충돌하게 되고, 포유류와 조류 모두 동일한 부모 클래스 Animal로부터 상속 받게 되어 상속 관계가 모호해지게 되는 문제가 발생합니다.

실 세계를 모델링 할 때 다중 상속은 쉽게 접할 수 있는 모델입니다만, 이와 같이 다중 상속에서 발생하는 여러 문제들로 인해 Java, C#, … 은 다중 상속을 지원하지 않으며, 지원하는 언어라 하더라도 가능한 다중 상속이 아닌 다른 방식으로 다중 상속 모델을 구현하려고 하고 있는 것 같습니다.

다중 상속 지원 언어: C++, Common Lisp (via Common Lisp Object System (CLOS)), EuLisp (via The EuLisp Object System TELOS), Curl, Dylan, Eiffel, Logtalk, Object REXX, Scala (via use of mixin classes), OCaml, Perl, POP-11, Python, R, Raku, and Tcl (built-in from 8.6 or via Incremental Tcl (Incr Tcl) in earlier versions), IBM System Object Model (SOM) runtime supports multiple inheritance

다중 상속을 지원하지 않는 언어: Swift, Java, C#, and Ruby

출처: wikipedia

현재 다중 상속 외에 다중 상속 모델을 구현할 수 있는 기능으로는 다중 인터페이스(multiple interface), 다중 트레이트(multiple trait)가 있으며, 또 다른 방식으로는 상위 클래스의 객체를 합성(composition)하는 방식으로 다중 상속 모델을 구현할 수 있습니다. 합성(composition)은 클래스가 다른 클래스의 객체를 가리키는 변수로 구성되는 방식입니다.

그러나 그 어떤 다른 방식을 이용하더라도 각 방식이 가지는 한계로 인하여 역시 간단치는 않은 것 같습니다. 그리고 다중 상속을 지원하는 언어마다 다이아몬드 상속 문제를 잘 알고 있으며 이에 따라 그것을 피할 해결책을 마련하여 지원하고 있으니 이를 잘 살펴보고 다중 상속을 사용할 것인지 말 것 인지를 신중히 결정하시는 것이 좋을 듯 합니다.

인터페이스로 구현된 다중 상속 모델

인터페이스로 다중 상속 모델을 구현할 때, 인터페이스는 추상 메소드의 구현 없이 시그니처만 선언해 놓은 것이기 때문에 다중 인터페이스(multiple interface)하더라도 메소드 중복의 모호성이 근원적으로 발생할 염려는 없으나, 인터페이스를 상속 받은 클래스마다 모든 추상 메소드를 반복해서 구현해 주어야 하기 때문에, 코드의 재사용성이나 유지 보수성의 저하를 가져옵니다.

인터페이스로 구현된 다중 상속 모델

트레이트로 구현된 다중 상속 모델

PHP는 다중 트레이트를 지원함으로 다중 상속이 가지고 있는 클래스들의 상속 관계의 모호성을 제거할 수 있으며, 비록 단일 상속만 지원하지만 디자이너가 코드 재사용이나 상속 복잡성 문제를 고려할 필요 없이 다중 상속 모델이 가능하도록 설계되었습니다.

전형적인 다이아몬드 타입의 모델에 의해 메소드 충돌이 일어났지만 트레이트의 충돌 방지 연산자를 명시적으로 사용함으로 문제가 발생하지 않으며, 트레이트의 복사·붙여넣기 메커니즘에 의해 컴파일할 때 트레이트 멤버가 모두 클래스 Bat에 조합됨에 따라 Animal의 중복 상속되는 문제는 애초부터 발생할 수 없습니다.

트레이트로 구현된 다중 상속 모델

그러나 트레이트는 컴파일할 때 클래스로 조합되는 실제적인 코드임으로 객체지향 언어로서의 클래스가 가지는 캡슐화와 같은 개념을 트레이트에 적용하기는 어렵습니다. 일반적인 상속(inheritance)조차도 private로 지정되지 않은 부모 클래스의 멤버가 자식 클래스에서 그대로 노출되고 있어 black-box가 아닌 white-box reuse라고 불리우는 상황에서, 트레이트는 이러한 상속에서 제공하는 캡슐화 조차도 기대하지 못한다는 한계를 가지고 있습니다. 따라서 프로젝트가 커지고 계층 구조 모델이 조금만 복잡해지면, 트레이트로 다중 상속 모델을 구현하기는 어려울 것입니다. 위의 예제는 예제일 뿐입니다.

트레이트는 서로 연관이 없는 복수의 클래스에서 필요한 기능들을 상속 계층을 해치지 않으면서 해당 클래스 내로 불러들여 사용할 때 적합하며, 이러한 기능들을 모듈화하여 코드의 재사용성을 향상시키기 위해 사용하는 용도가 적절한 것 같습니다.

합성으로 구현된 다중 상속 모델

합성(composition)은 트레이트와 같이 클래스 계층 구조와 무관하게 코드를 수평적으로 재사용 할 수 있도록 해줍니다. 트레이트와 달리 합성은 런타임에서 동적으로 객체의 타입이 결정되므로 매우 유연하게 시스템을 구성할 수 있으며, 또한 객체를 통해서만 접근하기 때문에 객체의 캡슐화가 잘 보존될 수 있습니다.

포유류 Mammalia와 조류 Birds는 서로 독립된 객체로 동작하기 때문에 전형적인 다이아몬드 타입의 모델에 관계없이 메소드 중복과 중복 상속으로 인한 모호성 문제는 발생하지 않습니다.

합성으로 구현된 다중 상속 모델

그러나 다이어몬드 상속에서 나타나는 중복 상속으로 인한 모호성 문제를 해결하였다고 해서 모든 문제가 해결된 것은 아닙니다. 포유류 Mammalia와 조류 Birds는 서로 독립된 객체마다 Animal 객체를 중복 해서 기억 장소를 할당 받고 있는 문제가 더 심각합니다. 다중 상속으로 인한 다이어몬드 상속에서 나타나는 문제는 크게 아래와 같이 두 가지로 정리할 수 있습니다.

  1. 상속 받은 여러 기반 클래스에 같은 이름의 멤버가 존재할 가능성(모호성)
  2. 하나의 클래스를 간접적으로 두 번 이상 상속 받을 가능성(모호성과 인스턴스 중복성)

위의 예제에서는 인스턴스 중복 문제가 계속 남게 됩니다. 기억 장소가 할당되는 면에서 다이어그램을 다시 그린다면 아래와 같습니다.

합성으로 구현된 다중 상속 모델에서 나타나는 중복 인스턴스 발생

따라서 다중 상속 모델을 합성으로 구현할 때에 중복 인스턴스 발생을 방지하려면, 포유류 Mammalia 클래스와 조류 Birds 클래스는 부모 클래스인 Animal을 상속 받는 것이 아니라 이 역시 아래와 같이 자식 클래스에서 합성으로 구현하여 처리해야만 합니다.

합성으로 구현된 다중 상속 모델(2)

다중 상속을 지원하는 C++에서 합성이 아닌 다중 상속에 의해 구현하였을 때, 중복 상속으로 인해 클래스 Animal에 해당하는 기억 장소가 중복되어 할당되지만 어떠한 컴파일 오류 없이 잘 실행됩니다. 이러한 문제를 해결하기 위하여 C++에서는 가상 기반 클래스(virtual base class) 기능을 지원하여 동일한 클래스가 중복되어 기억 장소를 할당되는 것을 방지하도록 해 줍니다.

위의 예제는 한 가지 예일 뿐이며, 다양한 상황에서 발생하는 다중 상속 문제는 여러 가지를 복합적으로 고려하여 설계하고 구현해 나가야 하리라 봅니다.

이와 관련하여 참고할 수 있는 인터넷 상의 자료로 정명수 님의 ‘대한민국 개발자와 객체지향 이야기 – 상속 설계, 상속 구현, …’이라는 글이 있는데 오래된 자료라 그림들이 다 깨져 있습니다. C++로 쓰여진 글이긴 하지만 상속 설계 및 구현과 관련된 중요한 개념들을 설명하고 있으니 찾아서 읽어 보시기 바랍니다. 정리되면 이 곳에도 올리도록 하겠습니다.

추상 클래스, 인터페이스, 트레이트, 합성을 복합적으로 적용하여 구현한 다중 상속 모델

추상 클래스, 인터페이스, 트레이트, 합성을 복합적으로 적용하여 다중 상속 모델을 구현한 예입니다. 추상 클래스는 추상 메소드 외에 일반 메소드를 정의할 수 있으나 단일 상속만 가능합니다.

추상클래스, 인터페이스, 트레이트, 합성으로 구현된 다중 상속 모델

답글 남기기