다중 상속
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
다중 상속은 객체 지향 프로그래밍에서 한 클래스가 둘 이상의 부모 클래스로부터 기능을 상속받는 것을 의미한다. 이를 통해 여러 클래스의 기능을 조합하여 새로운 클래스를 만들 수 있지만, 상속 관계가 복잡해지면 코드의 이해와 유지보수가 어려워질 수 있으며, '다이아몬드 문제'와 같은 모호성이 발생할 수 있다. 다이아몬드 문제는 두 클래스가 동일한 메서드를 상속받을 때 어떤 메서드를 호출해야 하는지 결정하기 어려운 상황을 말하며, C#, C++, Java, Python 등 다양한 프로그래밍 언어에서 다중 상속을 지원하거나, 다이아몬드 문제를 해결하기 위한 방법을 제공한다.
더 읽어볼만한 페이지
- 객체 지향 프로그래밍 - Is-a
Is-a 관계는 객체 지향 프로그래밍에서 한 유형이 다른 유형의 하위 유형임을 나타내는 관계로, 상속, 서브타이핑, 리스코프 치환 원칙과 관련되며, C++, Python, Java 등에서 표현된다. - 객체 지향 프로그래밍 - 객체 (컴퓨터 과학)
객체는 객체 지향 프로그래밍에서 데이터와 조작을 묶어 메시지를 수신하고, 프로그램의 개념을 표현하며 가시성과 재사용성을 높이는 실체이다.
다중 상속 | |
---|---|
개요 | |
정의 | 클래스 기반 객체 지향 프로그래밍 언어의 특징 클래스가 둘 이상의 슈퍼클래스로부터 속성과 메서드를 상속받을 수 있는 메커니즘 |
설명 | 단일 상속과 대조되는 개념 일부 객체 지향 언어에서 지원 |
특징 | |
장점 | 코드 재사용성 향상 클래스 간의 관계를 더 자연스럽게 모델링 가능 유연성 증가 |
단점 | 복잡성 증가 (다이아몬드 상속 문제 등) 이름 충돌 가능성 유지보수 어려움 |
다이아몬드 상속 문제 | |
설명 | 클래스 D가 클래스 B와 C를 상속받고, 클래스 B와 C가 모두 클래스 A를 상속받을 때 발생하는 문제 클래스 D가 클래스 A의 멤버를 어떻게 상속받아야 하는지에 대한 모호성 발생 |
해결 방법 | 가상 상속 (C++) 메서드 재정의 믹스인 (Mixin) |
지원하는 언어 | |
지원 | C++ Python Eiffel CLOS Perl Object Pascal Common Lisp Dylan Kotlin |
지원 안 함 | Java (인터페이스를 통한 다중 상속은 가능) C# (인터페이스를 통한 다중 상속은 가능) Smalltalk |
디자인 패턴 | |
Mixin | 다중 상속을 모방하기 위해 사용되는 디자인 패턴 기능을 "믹스"하여 클래스에 추가하는 방식 |
대안 | |
Traits (특징) | 다중 상속의 대안으로 제시되는 언어 기능 클래스가 구현 없이 인터페이스만 상속받는 방식 |
2. 다중 상속의 개념
객체 지향 프로그래밍에서 상속은 한 클래스(''자식'' 클래스)가 다른 클래스(''부모'' 클래스)의 메서드와 속성을 물려받는 것을 의미한다. 예를 들어, '포유류' 클래스는 먹고 번식하는 기능을 가지고 있으며, '고양이' 클래스는 '포유류' 클래스를 상속받아 이러한 기능을 가지면서 '쥐 쫓기'와 같은 고유한 기능을 추가할 수 있다.
다중 상속은 한 클래스가 둘 이상의 부모 클래스로부터 기능을 상속받는 것을 의미한다. 이를 통해 여러 클래스의 기능을 조합하여 새로운 클래스를 만들 수 있다.
2. 1. 다중 상속의 장점
다중 상속을 통해 프로그래머는 둘 이상의 완전히 직교하는 계층 구조를 동시에 사용할 수 있다. 예를 들어, '고양이'는 '만화 캐릭터', '애완동물', '포유류'를 상속받아 이 모든 클래스 내의 기능에 접근할 수 있다.[1] 이와 같이 다중 상속은 코드 재사용성을 극대화하여 중복 코드를 줄이고, 여러 클래스의 기능을 조합하여 복잡한 시스템을 효과적으로 모델링할 수 있다는 장점이 있다.2. 2. 다중 상속의 단점
'''다중 상속'''을 사용하면 상속 관계가 복잡해져 코드의 이해와 유지보수가 어려워질 수 있다. 특히, '다이아몬드 문제'와 같은 모호성이 발생할 수 있다.[6]
'''다이아몬드 문제'''는 두 개의 클래스 B와 C가 A에서 상속받고, 클래스 D가 B와 C 모두에서 상속받을 때 발생하는 모호성이다. 예를 들어 A에 B와 C가 재정의한 메서드가 있고, D가 이를 재정의하지 않으면, D는 B의 메서드를 상속받는 것인지, C의 메서드를 상속받는 것인지 알 수 없다.
GUI 소프트웨어 개발의 맥락에서 예를 들면, `Button` 클래스는 `Rectangle` 클래스(모양)와 `Clickable` 클래스(기능/입력 처리) 모두에서 상속받을 수 있으며, `Rectangle` 클래스와 `Clickable` 클래스 모두 `Object` 클래스에서 상속받는다. 이때 `Button` 객체에 대해 `equals` 메서드를 호출하는데, `Button` 클래스에 해당 메서드가 없고, `Rectangle` 또는 `Clickable`에 재정의된 `equals` 메서드가 있다면, 어떤 메서드를 호출해야 하는지 문제가 발생한다.
이러한 상황에서 클래스 상속 다이어그램의 모양이 다이아몬드와 같기 때문에 "다이아몬드 문제"라고 불린다.
3. 다이아몬드 문제
'''다이아몬드 문제'''는 다중 상속에서 발생하는 모호성 문제이다. 두 클래스 B와 C가 클래스 A를 상속받고, 클래스 D가 B와 C 모두에게서 상속받을 때, A에 B와 C가 재정의한 메서드가 있고 D가 이를 재정의하지 않으면, D는 어떤 버전의 메서드를 상속받는지 모호해진다.[6]
예를 들어, GUI 소프트웨어 개발에서 `Button` 클래스가 `Rectangle` 클래스(모양)와 `Clickable` 클래스(기능/입력 처리) 모두에서 상속받을 수 있다. `Rectangle` 클래스와 `Clickable` 클래스는 모두 `Object` 클래스에서 상속받는다. 이때 `Button` 객체에 대해 `equals` 메서드를 호출하는데, `Button` 클래스에는 해당 메서드가 없고 `Rectangle` 또는 `Clickable` 클래스에 재정의된 `equals` 메서드가 있다면, 어떤 메서드를 호출해야 하는가?
이러한 상황에서 클래스 상속 다이어그램의 모양이 다이아몬드와 같기 때문에 "다이아몬드 문제"라고 불린다.
3. 1. 다이아몬드 문제 해결 방법
각 프로그래밍 언어는 다이아몬드 문제를 해결하기 위해 다양한 방법을 제공한다.[6]- C# (C# 8.0부터)는 기본 인터페이스 메서드 구현을 허용한다. 기본 구현이 있는 비슷한 메서드를 가진 인터페이스 `Ia`와 `Ib`를 구현하는 클래스 `A`는 동일한 시그니처를 가진 두 개의 "상속된" 메서드를 갖게 되어 다이아몬드 문제를 일으킬 수 있다. 이 문제는 `A`가 메서드를 자체적으로 구현하도록 요구하거나, 호출자가 먼저 `A` 객체를 해당 인터페이스로 캐스팅하여 해당 메서드의 기본 구현을 사용하도록 강제함으로써 완화된다(예: `((Ia) aInstance).Method();`).
- C++: 기본적으로 각 상속 경로를 개별적으로 따르므로, `D` 객체는 두 개의 별도 `A` 객체를 포함한다. `A`의 멤버를 사용하려면 적절하게 한정해야 한다. `A`에서 `B`로의 상속과 `A`에서 `C`로의 상속이 모두 "virtual"로 표시된 경우(예: "class B : virtual public A"), C++는 하나의 `A` 객체만 생성하며, `A`의 멤버 사용은 올바르게 작동한다. 가상 상속과 비가상 상속이 혼합된 경우, 가상 `A`가 하나 있고 각 비가상 상속 경로에 대한 비가상 `A`가 있다. C++는 사용할 기능을 호출할 상위 클래스를 명시적으로 명시해야 한다(예: `Worker::Human.Age`). C++는 어떤 슈퍼클래스를 사용할지 한정할 방법이 없으므로 명시적인 반복 상속을 지원하지 않는다. C++는 또한 가상 상속 메커니즘을 통해 다중 클래스의 단일 인스턴스를 생성할 수 있도록 한다(예: `Worker::Human` 및 `Musician::Human`은 동일한 객체를 참조한다).
- Common Lisp CLOS는 합리적인 기본 동작과 이를 재정의할 수 있는 기능을 모두 제공한다. 기본적으로 메서드는 `D,B,C,A` 순서로 정렬된다. (B가 클래스 정의에서 C보다 먼저 작성된 경우) 가장 구체적인 인수 클래스를 가진 메서드가 선택되고(D>(B,C)>A), 하위 클래스 정의에서 상위 클래스가 명명된 순서대로(B>C) 선택된다. 프로그래머는 특정 메서드 해결 순서를 지정하거나 메서드를 결합하기 위한 규칙을 명시하여 이를 재정의할 수 있다. 이를 메서드 결합이라고 하며, 완전히 제어할 수 있다. MOP (메타객체 프로토콜)는 또한 시스템의 안정성에 영향을 주지 않고 상속, 동적 디스패치, 클래스 인스턴스화 및 기타 내부 메커니즘을 수정하는 수단을 제공한다.
- Curl은 명시적으로 ''shared''로 표시된 클래스만 반복적으로 상속할 수 있도록 한다. 공유 클래스는 클래스의 각 일반 생성자에 대해 ''보조 생성자''를 정의해야 한다. 일반 생성자는 하위 클래스 생성자를 통해 공유 클래스에 대한 상태가 처음 초기화될 때 호출되고, 보조 생성자는 다른 모든 하위 클래스에 대해 호출된다.
- 에펠에서 조상의 기능은 select 및 rename 지시어를 사용하여 명시적으로 선택된다. 이를 통해 기본 클래스의 기능을 자손 간에 공유하거나 각 자손에게 기본 클래스의 개별 사본을 제공할 수 있다. 에펠은 조상 클래스에서 상속된 기능을 명시적으로 결합하거나 분리할 수 있도록 한다. 에펠은 이름과 구현이 동일한 경우 기능을 자동으로 결합한다. 클래스 작성자는 상속된 기능을 이름을 변경하여 분리할 수 있다. 다중 상속은 에펠 개발에서 빈번하게 발생한다. 예를 들어, 널리 사용되는 데이터 구조 및 알고리즘 에펠베이스 라이브러리의 유효 클래스의 대부분은 둘 이상의 부모를 가지고 있다.[7]
- Go는 컴파일 시간에 다이아몬드 문제를 방지한다. 구조체 `D`가 메서드 `F()`를 모두 가지고 있는 두 개의 구조체 `B`와 `C`를 포함하여 인터페이스 `A`를 충족하는 경우, `D.F()`가 호출되거나 `D`의 인스턴스가 `A` 타입의 변수에 할당되면 컴파일러는 "모호한 선택자"에 대해 불만을 제기한다. `B`와 `C`의 메서드는 `D.B.F()` 또는 `D.C.F()`를 사용하여 명시적으로 호출할 수 있다.
- Java 8은 인터페이스에 기본 메서드를 도입한다. `A,B,C`가 인터페이스인 경우, `B,C`는 각각 `A`의 추상 메서드에 대한 다른 구현을 제공하여 다이아몬드 문제를 일으킬 수 있다. 클래스 `D`는 해당 메서드를 다시 구현해야 하거나(본문은 단순히 호출을 상위 구현 중 하나로 전달할 수 있음) 모호성은 컴파일 오류로 거부된다.[8] Java 8 이전에는 Java가 다중 상속을 지원하지 않았고 인터페이스 기본 메서드를 사용할 수 없었기 때문에 다이아몬드 문제 위험에 노출되지 않았다.
- 자바FX 스크립트 버전 1.2에서는 믹스인을 사용하여 다중 상속을 허용한다. 충돌이 발생하는 경우 컴파일러는 모호한 변수 또는 함수의 직접적인 사용을 금지한다. 각 상속된 멤버는 여전히 객체를 관심 있는 믹스인으로 캐스팅하여 액세스할 수 있다(예: `(개인 as Person).printInfo();`).
- Kotlin은 인터페이스의 다중 상속을 허용하지만, 다이아몬드 문제 시나리오에서 자식 클래스는 상속 충돌을 일으키는 메서드를 재정의하고 사용할 상위 클래스 구현을 지정해야 한다. 예를 들어 `super<선택된 상위 인터페이스>.someMethod()`
- Logtalk는 인터페이스 및 구현 다중 상속을 모두 지원하며, 기본 충돌 해결 메커니즘에 의해 마스크될 메서드의 이름 변경 및 액세스를 모두 제공하는 메서드 ''별칭''의 선언을 허용한다.
- OCaml에서 상위 클래스는 클래스 정의 본문에서 개별적으로 지정된다. 메서드(및 속성)는 동일한 순서로 상속되며, 새로 상속된 각 메서드는 기존 메서드를 재정의한다. OCaml은 모호성에서 사용할 메서드 구현을 해결하기 위해 클래스 상속 목록의 마지막 일치 정의를 선택한다. 기본 동작을 재정의하려면 원하는 클래스 정의를 사용하여 메서드 호출을 한정하면 된다.
- Perl은 상속할 클래스 목록을 정렬된 목록으로 사용한다. 컴파일러는 슈퍼클래스 목록을 깊이 우선 탐색하거나 클래스 계층 구조의 C3 선형화를 사용하여 처음 찾은 메서드를 사용한다. 다양한 확장은 대체 클래스 구성 체계를 제공한다. 상속 순서는 클래스 의미에 영향을 미친다. 위의 모호성에서 클래스
B
와 해당 조상은 클래스C
와 해당 조상보다 먼저 확인되므로A
의 메서드는B
를 통해 상속된다. 이는 Io 및 Picolisp와 공유된다. Perl에서는 `mro` 또는 기타 모듈을 사용하여 C3 선형화 또는 기타 알고리즘을 사용함으로써 이 동작을 재정의할 수 있다.[9] - Python은 Perl과 동일한 구조를 가지고 있지만, Perl과 달리 언어의 구문에 포함되어 있다. 상속 순서는 클래스 의미에 영향을 미친다. 파이썬은 새로운 스타일의 클래스가 도입될 때 이를 처리해야 했으며, 이 클래스는 모두 공통 조상 `object`를 가지고 있다. 파이썬은 C3 선형화(또는 메서드 해결 순서(MRO)) 알고리즘을 사용하여 클래스 목록을 만든다. 해당 알고리즘은 두 가지 제약 조건을 적용한다. 자식은 부모보다 먼저 오고 클래스가 여러 클래스에서 상속되는 경우 기본 클래스의 튜플에 지정된 순서로 유지된다(그러나 이 경우 상속 그래프에서 높은 일부 클래스가 그래프에서 낮은 클래스보다 먼저 올 수 있음).[10] 따라서 메서드 해결 순서는
D
,B
,C
,A
이다.[11] - 루비 클래스는 정확히 하나의 부모를 가지지만 여러 ''모듈''에서 상속할 수도 있다. 루비 클래스 정의가 실행되고 메서드의 (재)정의는 실행 시 이전에 존재하던 모든 정의를 가린다. 런타임 메타 프로그래밍이 없는 경우 이는 가장 오른쪽 깊이 우선 해결과 거의 동일한 의미를 갖는다.
- Scala는 ''트레이트''의 다중 인스턴스화를 허용하며, 이는 클래스 계층 구조와 트레이트 계층 구조 간의 구분을 추가하여 다중 상속을 허용한다. 클래스는 단일 클래스에서만 상속할 수 있지만 원하는 만큼 많은 트레이트를 믹스인할 수 있다. 스칼라는 확장된 '트레이트'의 가장 오른쪽 깊이 우선 검색을 사용하여 메서드 이름을 해결한 다음, 결과 목록에서 각 모듈의 마지막 발생을 제외한 모든 것을 제거한다. 따라서 해결 순서는 [
D
,C
,A
,B
,A
]이며, [D
,C
,B
,A
]로 축소된다. - Tcl은 여러 부모 클래스를 허용한다. 클래스 선언의 지정 순서는 C3 선형화 알고리즘을 사용하여 멤버에 대한 이름 해결에 영향을 미친다.[12]
단일 상속만 허용하는 언어는 다이아몬드 문제를 가지지 않는다. 그 이유는 이러한 언어가 메서드의 반복 또는 배치에 관계없이 상속 체인의 모든 수준에서 모든 메서드의 구현을 하나만 갖기 때문이다. 일반적으로 이러한 언어는 클래스가 여러 프로토콜을 구현하도록 허용하며, 이는 Java에서는 인터페이스라고 한다. 이러한 프로토콜은 메서드를 정의하지만 구체적인 구현을 제공하지 않는다. 이 전략은 ActionScript, C#, D, Java, Nemerle, 오브젝트 파스칼, Objective-C, 스몰토크, Swift 및 PHP에서 사용되었다.[13] 이러한 모든 언어는 클래스가 여러 프로토콜을 구현하도록 허용한다.
Ada, C#, Java, Object Pascal, Objective-C, Swift 및 PHP는 인터페이스의 다중 상속을 허용한다(Objective-C 및 Swift에서는 프로토콜이라고 함). 인터페이스는 동작을 구현하지 않고 메서드 시그니처를 지정하는 추상 기본 클래스와 같다. (Java 7 버전까지의 "순수" 인터페이스는 인터페이스에서 구현 또는 인스턴스 데이터를 허용하지 않는다.) 그럼에도 불구하고, 여러 인터페이스가 동일한 메서드 시그니처를 선언하는 경우에도 해당 메서드가 상속 체인 어디에서든 구현(정의)되는 즉시 체인 상단의 해당 메서드 구현을 재정의한다(슈퍼클래스에서). 따라서 상속 체인의 특정 수준에서 모든 메서드의 구현은 최대 하나만 있을 수 있다. 따라서 단일 상속 메서드 구현은 인터페이스의 다중 상속에서도 다이아몬드 문제를 나타내지 않는다. Java 8 및 C# 8에서 인터페이스에 대한 기본 구현이 도입되면서 다이아몬드 문제를 생성하는 것이 여전히 가능하지만, 이는 컴파일 시간 오류로만 나타난다.
4. 구현
다중 상속을 지원하는 프로그래밍 언어로는 C++, Common Lisp (Common Lisp 객체 시스템(CLOS)), EuLisp (The EuLisp 객체 시스템 TELOS), Curl, Dylan, Eiffel, Logtalk, Object REXX, Scala (mixin 클래스 사용), OCaml, Perl, POP-11, Python, R, Raku, Tcl (8.6부터 내장, 이전 버전에서는 Incremental Tcl (Incr Tcl)을 통해[4][5]) 등이 있다.
IBM System Object Model (SOM) 런타임은 다중 상속을 지원하며, SOM을 사용하는 모든 프로그래밍 언어는 여러 기본 클래스에서 상속된 새로운 SOM 클래스를 구현할 수 있다.
Swift, Java, Fortran (2003 개정판부터), C#, Ruby와 같은 일부 객체 지향 언어는 ''단일 상속''을 구현하지만, 프로토콜 또는 ''인터페이스''를 통해 다중 상속의 일부 기능을 제공한다.
PHP는 특정 메서드 구현을 상속하기 위해 트레이트 클래스를 사용하며, Ruby는 여러 메서드를 상속하기 위해 모듈을 사용한다.
참조
[1]
논문
Controversy: The Case Against Multiple Inheritance in C++
1991-01-01
[2]
논문
Controversy: The Case For Multiple Inheritance in C++
1991-04-01
[3]
웹사이트
Traits: Composable Units of Behavior
http://web.cecs.pdx.[...]
2016-10-21
[4]
웹사이트
incr Tcl
http://blog.tcl.tk/6[...]
2020-04-14
[5]
웹사이트
Introduction to the Tcl Programming Language
https://www2.lib.uch[...]
2020-04-14
[6]
웹사이트
Java and C++: A critical comparison
http://objectmentor.[...]
2016-10-21
[7]
웹사이트
Standard ECMA-367
http://www.ecma-inte[...]
2016-10-21
[8]
웹사이트
State of the Lambda
http://cr.openjdk.ja[...]
2016-10-21
[9]
웹사이트
perlobj
http://perldoc.perl.[...]
2016-10-21
[10]
웹사이트
The Python 2.3 Method Resolution Order
https://www.python.o[...]
2016-10-21
[11]
웹사이트
Unifying types and classes in Python 2.2
https://www.python.o[...]
2016-10-21
[12]
웹사이트
Manpage of class
http://www.tcl.tk/ma[...]
2016-10-21
[13]
웹사이트
Object Interfaces - Manual
http://php.net/manua[...]
2016-10-21
[14]
문서
Tcl Advocacy
http://wiki.tcl.tk/5[...]
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com