다중 상속을 싫어하는“실제”이유가 있습니까?


123

나는 언어에서 다중 상속을 지원한다는 아이디어를 항상 좋아했습니다. 대부분의 경우 의도적으로 무시되었지만 "교체"라고 생각되는 것은 인터페이스입니다. 인터페이스는 단순히 다중 상속이 수행하는 모든 동일한 접지를 다루지 않으며, 이러한 제한으로 인해 더 많은 상용구 코드가 생길 수 있습니다.

내가 들어 본 유일한 기본 이유는 기본 클래스 의 다이아몬드 문제 입니다. 나는 그것을 받아 들일 수 없다. 나에게, 그것은 엄청 많이처럼, "음, 그건 벗기 를 망치는, 그래서 자동으로 나쁜 생각입니다." 그래도 프로그래밍 언어로 무엇이든 망칠 수 있습니다. 적어도 더 철저한 설명 없이는 이것을 심각하게 받아 들일 수 없습니다.

이 문제를 알고 있다는 것은 전투의 90 %입니다. 또한 몇 년 전 "봉투"알고리즘 또는 이와 유사한 것 (이 링이 종을 울리는가?)과 관련된 일반적인 해결 방법에 대해 들었습니다.

다이아몬드 문제와 관련하여 내가 생각할 수있는 유일한 잠재적 문제는 타사 라이브러리를 사용하려고하는데 해당 라이브러리에서 관련이없는 것으로 보이는 두 개의 클래스가 공통 기본 클래스를 가지고 있음을 알 수없는 경우입니다. 예를 들어, 간단한 언어 기능인 문서는 실제로 다이아몬드를 컴파일하기 전에 다이아몬드를 만들려는 의도를 명시 적으로 요구할 수 있습니다. 그러한 특징으로, 다이아몬드의 생성은 의도적이거나 무모하거나이 함정을 알지 못하기 때문입니다.

모든 사람들이 말하기를 ... 대부분의 사람들이 여러 상속을 싫어하는 실제적인 이유 가 있습니까? 아니면 모두 좋은 것보다 더 많은 해를 입히는 히스테리입니까? 여기에 보이지 않는 것이 있습니까? 감사합니다.

자동차는 바퀴 달린 차량, KIASpectra는 자동차 및 전자, KIASpectra는 라디오를 확장합니다. KIASpectra에 전자 제품이 포함되지 않은 이유는 무엇입니까?

  1. 전자 이기 때문 입니다 . 상속 대 구성은 항상 is-a 관계 대 has-a 관계 여야합니다.

  2. 전자 이기 때문 입니다 . 와이어, 회로 보드, 스위치 등이 모두 위와 아래에 있습니다.

  3. 전자 이기 때문 입니다 . 겨울에 배터리가 방전 되면 모든 바퀴가 갑자기 빠진 것처럼 큰 어려움에 처하게됩니다.

왜 인터페이스를 사용하지 않습니까? 예를 들어 3 번을 봅시다. 나는 다시이 이상을 쓰고 싶지 않아, 난 정말 이 하나를 수행하는 몇 가지 기괴한 프록시 도우미 클래스를 만들려하지 않습니다

private void runOrDont()
{
    if (this.battery)
    {
        if (this.battery.working && this.switchedOn)
        {
            this.run();
            return;
        }
    }
    this.dontRun();
}

(우리는 그 구현이 좋은지 나쁜지를 알지 못합니다.) WheeledVehicle의 어떤 것과도 관련이없는 Electronic과 관련된 이러한 기능 중 일부 가 어떻게 존재할지 상상할 수 있습니다 .

거기에 해석의 여지가 있기 때문에 그 예를 정할 것인지 확실하지 않았습니다. 비행기 확장 차량 및 FlyingObject 및 Bird 확장 Animal 및 FlyingObject 또는 훨씬 더 순수한 예를 생각할 수도 있습니다.


24
그것은 또한 구성에 대한 상속을 장려합니다 ... (다른 방법으로해야 할 때)
래칫 괴물

34
"고정시킬 수 있습니다"는 기능을 제거하는 데 완벽하게 유효한 이유입니다. 현대 언어 디자인은 그 시점에서 다소 모호하지만 일반적으로 언어를 "제한의 힘"과 "유연한 힘"으로 분류 할 수 있습니다. 둘 다 "올바른"것도 아니라고 생각합니다. 둘 다 강력한 장점이 있습니다. MI는 Evil에 자주 사용되는 것 중 하나이므로 제한적인 언어로 제거합니다. 융통성있는 사람들은 그렇지 않습니다. "보다 자주"는 "문자 적으로 항상"이 아니기 때문입니다. 즉, 믹스 인 / 특성은 일반적인 경우에 더 나은 솔루션이라고 생각합니다.
Phoshi

8
인터페이스를 넘어 여러 언어로 된 다중 상속에 대한 안전한 대안이 있습니다. 스칼라를 확인하십시오 Traits-옵션으로 구현 된 인터페이스처럼 작동하지만 다이아몬드 문제와 같은 문제를 방지하는 데 도움이되는 몇 가지 제한 사항이 있습니다.
KChaloux

5
또한 이전에 닫힌 질문을 참조하십시오 : programmers.stackexchange.com/questions/122480/…
Doc Brown

19
KiaSpectra하지 Electronic ; 그것은 전자 제품 을 가지고 있으며 ElectronicCar(확장 될 것입니다 Car)
Brian S

답변:


68

많은 경우 사람들은 상속을 사용하여 클래스에 특성을 제공합니다. 예를 들어 페가수스를 생각해보십시오. 다중 상속을 사용하면 페가수스는 말과 새를 확장한다고 말하고 싶을 것입니다. 왜냐하면 버드를 날개가있는 동물로 분류했기 때문입니다.

그러나 조류에는 페 가시에는없는 다른 특성이 있습니다. 예를 들어, 새가 알을 낳고 페 가시는 생을 낳습니다. 상속이 공유 특성을 전달하는 유일한 수단이라면, 페가수스에서 알을 낳는 특성을 배제 할 방법이 없습니다.

일부 언어는 언어 내에서 특성을 명시적인 구성으로 만들었습니다. 다른 사람은 언어에서 MI를 제거하여 해당 방향으로 부드럽게 안내합니다. 어느 쪽이든, 나는 "이것이 제대로 작동하기 위해서는 MI가 정말로 필요하다"고 생각한 단일 사례를 생각할 수 없다.

또한 상속이 무엇인지 논의 해 보자. 클래스에서 상속을 받으면 해당 클래스에 대한 종속성을 가지게되지만 클래스가 지원하는 계약을 암시 적 및 명시 적으로 지원해야합니다.

사각형에서 상속되는 사각형의 고전적인 예를 보자. 사각형은 length 및 width 속성과 getPerimeter 및 getArea 메서드를 노출합니다. 정사각형은 길이와 너비를 재정 의하여 하나가 설정되면 다른 하나가 getPerimeter와 일치하도록 설정되고 getArea가 동일하게 작동합니다 (주변의 경우 2 * 길이 + 2 * 너비와 영역의 길이 * 너비).

이 사각형 구현을 사각형으로 대체하면 단일 테스트 사례가 중단됩니다.

var rectangle = new Square();
rectangle.length= 5;
rectangle.width= 6;
Assert.AreEqual(30, rectangle.GetArea()); 
//Square returns 36 because setting the width clobbers the length

단일 상속 체인으로 물건을 올바르게 얻는 것은 충분히 어렵습니다. 믹스에 다른 것을 추가하면 더욱 악화됩니다.


제가 MI에서 Pegasus와 함께 언급 한 함정과 Rectangle / Square 관계는 둘 다 경험이없는 클래스 디자인의 결과입니다. 기본적으로 다중 상속을 피하는 것은 초보 개발자가 발에 직접 쏘지 않도록 도와주는 방법입니다. 모든 디자인 원칙과 마찬가지로,이를 기반으로 한 훈련과 훈련을 받으면 언제부터 벗어날 수 있는지 알아낼 수 있습니다. 전문가 수준에서 Dreyfus 기술 획득 모델을 참조하십시오. 귀하의 고유 지식은 최대 / 원칙에 대한 의존성을 초월합니다. 규칙이 적용되지 않으면 "느낌"을 느낄 수 있습니다.

그리고 나는 MI가 눈살을 찌푸리는 이유에 대한 "실제 세계"의 예에 다소 부정한 것에 동의합니다.

UI 프레임 워크를 봅시다. 특히 처음에는 브러시가 단순히 다른 두 가지의 조합 인 것처럼 보일 수있는 몇 가지 위젯을 살펴 보겠습니다. 콤보 박스처럼. ComboBox는 DropDownList를 지원하는 TextBox입니다. 즉, 값을 입력하거나 미리 지정된 값 목록에서 선택할 수 있습니다. 순진한 접근 방식은 TextBox 및 DropDownList에서 ComboBox를 상속하는 것입니다.

그러나 텍스트 상자는 사용자가 입력 한 것에서 그 가치를 얻습니다. DDL은 사용자가 선택한 것에서 가치를 얻습니다. 누가 우선합니까? DDL은 원래 값 목록에없는 입력을 확인하고 거부하도록 설계되었을 수 있습니다. 우리는 그 논리를 무시합니까? 즉, 상속자가 재정의 할 내부 논리를 노출해야합니다. 또는 하위 클래스를 지원하기 위해 존재하는 기본 클래스에 논리를 추가하십시오 ( Dependency Inversion Principle 위반 ).

MI를 피하면이 함정을 완전히 회피 할 수 있습니다. UI 위젯의 재사용 가능한 공통 특성을 추출하여 필요할 때 적용 할 수 있습니다. WPF 의 프레임 워크 요소가 다른 프레임 워크 요소가 상위 프레임 워크 요소에서 상속하지 않고 사용할 수있는 특성을 제공 할 수 있도록하는 WPF 첨부 특성 이 이에 대한 훌륭한 예입니다 .

예를 들어, 그리드는 WPF의 레이아웃 패널이며 그리드의 배열에서 하위 요소를 배치 할 위치를 지정하는 열 및 행 첨부 속성이 있습니다. 연결된 속성이 없으면 Grid 내에서 Button을 정렬하려면 Button을 Grid에서 파생해야하므로 Column 및 Row 속성에 액세스 할 수 있습니다.

개발자는이 개념을 더 취해 구성 요소를 구성하는 방법으로 연결된 속성을 사용했습니다 (예를 들어 WPF에 DataGrid가 포함되기 전에 작성된 속성을 사용하여 정렬 가능한 GridView를 만드는 방법에 대한 게시물 ). 이 접근 방식은 Attached Behaviors 라는 XAML 디자인 패턴으로 인식되었습니다 .

이것이 다중 상속이 일반적으로 찌그러지는 이유에 대한 약간의 통찰력을 제공하기를 바랍니다.


14
"남자가 정말로 이것을하기 위해서는 MI가 필요하다"-exmaple에 대한 나의 대답을보십시오. 간단히 말해서, is-a 관계를 만족시키는 직교 개념은 MI에 의미가 있습니다. 그들은 매우 드물지만 존재합니다.
MrFox

13
남자, 나는 그 사각형 사각형 예가 싫어. 변경이 필요없는 더 많은 조명 예제가 있습니다.
asmeurer

9
나는 "실제적인 예를 제공하라"는 가상의 생물에 대해 이야기하는 것이 좋았다. 탁월한 아이러니 +1
jjathman

3
따라서 상속이 나쁘기 때문에 다중 상속이 나쁘다는 것을 말하고 있습니다 (예는 모두 단일 인터페이스 상속에 관한 것입니다). 따라서이 답변만으로 언어는 상속과 인터페이스를 제거하거나 다중 상속을 가져야합니다.
ctrl-alt-delor

12
나는이 예들이 실제로 효과가 있다고 생각하지 않습니다 ... 페가수스는 새와 말이라고 말하지 않습니다 ... 당신은 그것이 WingedAnimal과 HoofedAnimal이라고 말합니다. 정사각형과 직사각형 예제는 자신이 주장한 정의에 따라 생각하는 것과 다르게 행동하는 객체로 자신을 발견하기 때문에 조금 더 의미가 있습니다. 그러나 나는이 상황이 이것을 잡지 않은 프로그래머의 잘못이라고 생각합니다 (실제로 이것이 문제가되는 것으로 판명되었습니다).
richard

60

여기에 보이지 않는 것이 있습니까?

다중 상속을 허용하면 함수 레이아웃과 가상 디스패치에 대한 규칙과 객체 레이아웃에 대한 언어 구현이 결정적으로 까다로워집니다. 이들은 언어 디자이너 / 구현 자에게 상당한 영향을 미치며 이미 높은 수준을 높여 언어를 완성하고 안정적으로 채택합니다.

내가 본 (그리고 때때로 만든) 또 다른 일반적인 주장은 두 가지 이상의 기본 클래스를 가짐으로써 객체가 거의 단일 책임 원칙을 거의 위반한다는 것입니다. 2 개 이상의 기본 수업은 자체 책임이있는 훌륭한 자체 포함 수업이거나 (위반을 유발하는) 단일 응집력있는 책임을 만들기 위해 서로 협력하는 부분 / 추상 유형입니다.

이 경우에는 세 가지 시나리오가 있습니다.

  1. A는 B에 대해 아무것도 모릅니다. 훌륭합니다. 운이 좋기 때문에 수업을 결합 할 수 있습니다.
  2. A는 B에 대해 알고 있습니다-A는 왜 B에서 상속받지 않았습니까?
  3. A와 B는 서로에 대해 알고 있습니다. 왜 하나의 수업을 만들지 않았습니까? 이러한 것들을 결합하여 부분적으로 대체 할 수있게하면 어떤 이점이 있습니까?

개인적으로, 다중 상속에는 랩이 좋지 않다고 생각하고, 잘 구성된 특성 스타일 구성 시스템은 실제로 강력하고 유용 할 것입니다 ...하지만 잘못 구현할 수있는 많은 방법이 있습니다. C ++과 같은 언어에서는 좋은 생각이 아닙니다.

[편집] 당신의 예와 관련하여, 그것은 터무니 없습니다. 기아 에는 전자 제품이 있습니다. 그것은 엔진을. 마찬가지로, 전자 장치 에는 자동차 배터리와 같은 전원이 있습니다. 상속, 다중 상속은 물론 장소가 없습니다.


8
또 다른 흥미로운 질문은 기본 클래스 + 기본 클래스를 초기화하는 방법입니다. 캡슐화> 상속.
jozefg

"특성 스타일의 조성물의 잘 시스템은 정말 / 강력한 도움이 될 것입니다 ..." - 이들은으로 알려져 유지 mixin , 그들은 이다 유용 / 강력. MI를 사용하여 믹스 인을 구현할 수 있지만 믹스 인에는 다중 상속이 필요하지 않습니다. 일부 언어 는 MI없이 기본적으로 믹스 인을 지원합니다.
BlueRaja-대니 Pflughoeft

특히 Java의 경우 이미 여러 인터페이스와 INVOKEINTERFACE가 있으므로 MI는 명백한 성능 영향을 미치지 않습니다.
다니엘 루바 로프

Tetastyn, 나는 그 예가 나쁘고 그의 질문에 답하지 않았다는 것에 동의합니다. 상속은 형용사 (전자)를위한 것이 아니라 명사 (차량)를위한 것입니다.
richard

29

그것이 허용되지 않는 유일한 이유는 사람들이 발에 쉽게 쏠 수 있기 때문입니다.

이런 종류의 토론에서 일반적으로 따르는 것은 발을 쏘지 않는 안전보다 도구를 갖는 유연성이 더 중요한지 여부에 대한 논쟁입니다. 프로그래밍의 다른 것들과 마찬가지로 그 대답은 상황에 달려 있기 때문에 그 주장에 대한 정답은 확실하지 않습니다.

개발자가 MI에 익숙하고 MI가 현재하고있는 작업의 맥락에서 의미가 있다면,이를 지원하지 않는 언어로 그리워 할 것입니다. 동시에 팀이 편안하지 않거나 실제로 필요하지 않고 사람들이 '그냥 할 수 있기 때문에'그것을 사용하면 반 생산적입니다.

그러나 아닙니다. 다중 상속이 나쁜 생각임을 입증하는 완전히 설득력있는 절대적인 주장은 없습니다.

편집하다

이 질문에 대한 답변은 만장일치로 보입니다. 나는 악마의 옹호자가되기 위해 여러 상속의 좋은 예를 제시 할 것이다.

자본 시장 애플리케이션을 설계한다고 가정하십시오. 유가 증권에 대한 데이터 모델이 필요합니다. 일부 증권은 주식 상품 (주식, 부동산 투자 신탁 등)이고 다른 증권은 부채 (채권, 회사 채권), 다른 증권은 파생 상품 (옵션, 선물)입니다. 따라서 MI를 피한다면 매우 명확하고 간단한 상속 트리를 만들 수 있습니다. 주식은 주식을, 채권은 부채를 상속합니다. 지금까지는 훌륭하지만 파생 상품은 어떻습니까? 그들은 주식과 같은 제품 또는 직불과 같은 제품을 기반으로 할 수 있습니까? 좋습니다. 상속 트리 분기를 더 많이 만들 것입니다. 일부 파생 상품은 지분 상품, 부채 상품 또는 둘 다를 기반으로하지 않습니다. 상속 트리가 복잡해지고 있습니다. 그런 다음 비즈니스 분석가가 와서 인덱스 증권 (인덱스 옵션, 인덱스 미래 옵션)을 지원한다고 알려줍니다. 그리고 이러한 것들은 주식, 부채 또는 파생 상품을 기반으로 할 수 있습니다. 지저분 해지고 있습니다! 내 인덱스 미래 옵션이 주식-> 주식-> 옵션-> 인덱스를 파생합니까? 주식-> 주식-> 색인-> 옵션이 아닌 이유는 무엇입니까? 언젠가 코드에서 두 가지를 모두 발견하면 어떻게 되나요?

여기서 문제는 이러한 기본 유형이 자연스럽게 서로 파생되지 않는 순열에 혼합 될 수 있다는 것입니다. 의해 정의 된 오브젝트 A는 관계 때문에 조성물은 전혀 의미가 없다. 다중 상속 (또는 유사한 믹스 인 개념)은 여기에서 유일한 논리적 표현입니다.

이 문제에 대한 실제 솔루션은 다중 상속을 사용하여 주식, 부채, 파생, 색인 유형을 정의하고 혼합하여 데이터 모델을 작성하는 것입니다. 이렇게하면 의미가 있고 재사용이 쉬운 객체를 만들 수 있습니다.


27
나는 금융에서 일했다. 재무 소프트웨어에서 기능 프로그래밍이 OO보다 선호되는 이유가 있습니다. 그리고 당신은 그것을 지적했습니다. MI는이 문제를 해결하지 않아 악화시킵니다. 내가 금융 클라이언트를 다룰 때 "이것은 ... 이것과 같다 ..."라는 말을 몇 번이나 들었는지 말해 줄 수 없다.
Michael Brown

8
이것은 나에게 인터페이스로 매우 해결할 수있는 것처럼 보입니다 ... 사실, 그것은 다른 것 보다 인터페이스에 적합합니다. Equity그리고 Debt둘 다 구현 ISecurity합니다. DerivativeISecurity속성을. ISecurity적절한 경우 자체가 될 수 있습니다 (재정을 모릅니다). IndexedSecurities또한 기반이 될 수있는 유형에 적용되는 인터페이스 속성이 포함됩니다. 그들이 모두 ISecurity라면, 그들은 모두 ISecurity속성을 가지며 임의로 중첩 될 수 있습니다 ...
Bobson

11
@Bobson 문제는 각 구현 자에 대한 인터페이스를 다시 구현해야한다는 점에서 발생하며 많은 경우에 동일합니다. 위임 / 구성을 사용할 수 있지만 개인 구성원에 대한 액세스 권한이 손실됩니다. 그리고 자바에는 델리게이트 / 람다가 없기 때문에 말 그대로 좋은 방법은 없습니다. Aspect4J와 같은 Aspect 툴을 제외하고
Michael Brown

10
@Bobson은 Mike Brown이 말한 것과 정확히 일치합니다. 예, 인터페이스가있는 솔루션을 디자인 할 수 있지만 문제가 발생할 수 있습니다. 인터페이스를 사용하는 직감은 매우 정확하지만 믹스 인 / 다중 상속에 대한 숨겨진 욕망입니다 :).
MrFox

11
이것은 OO 프로그래머가 상속의 관점에서 생각하는 것을 선호하고 종종 위임을 유효한 OO 접근 방식으로 받아들이지 않는 이상한 점에 직면합니다.이 방법은 일반적으로 더 얇고 유지 관리가 쉽고 확장 가능한 솔루션을 제공합니다. 재무 및 건강 관리 분야에서 일한 결과 MI가 매년 세금 및 건강 법률이 변경되고 지난해 개체 정의가 지난 해에 유효하지 않음으로 인해 MI가 만들 수있는 혼란을 겪었습니다. 상호 교환 가능해야합니다). 컴포지션은 테스트하기 쉬운 코드가 더 적으며 시간이 지남에 따라 비용이 적게 듭니다.
Shaun Wilson

11

다른 대답은 대부분 이론에 들어가는 것 같습니다. 그래서 여기에 단순화 된 구체적인 파이썬 예제가 있습니다. 실제로 엄청난 양의 리팩토링이 필요했습니다.

class Foo(object):
   def zeta(self):
      print "foozeta"

class Bar(object):
   def zeta(self):
      print "barzeta"

   def barstuff(self):
      print "barstuff"
      self.zeta()

class Bang(Foo, Bar):
   def stuff(self):
      self.zeta()
      print "---"
      self.barstuff()

z = Bang()
z.stuff()

Bar는 자체 구현이 있다고 가정하여 작성되었으며 zeta()일반적으로 꽤 좋은 가정입니다. 서브 클래스는 올바른 작업을 수행하기 위해 서브 클래스를 적절하게 대체해야합니다. 불행히도, 그 이름들은 우연히 똑 같았습니다. 그것들은 다소 다른 일을했지만 Bar지금은 Foo구현을 호출했습니다 .

foozeta
---
barstuff
foozeta

오류가 발생하지 않고 응용 프로그램이 약간 잘못 작동하기 시작하고 (생성하는 Bar.zeta) 코드 변경으로 인해 문제가있는 곳이 아닌 것 같습니다.


전화를 통해 다이아몬드 문제를 어떻게 피할 수 super()있습니까?
Panzercrisis

@Panzercrisis 죄송합니다. 그 부분은 신경 쓰지 마십시오. 파이썬은 다이아몬드 모양의 상속에 의한 별도의 문제가 있었다 - 나는 "다이아몬드 문제는"보통에 참조하는 문제 misremembered super()주변에있어
Izkata

5
C ++의 MI가 훨씬 더 잘 설계되어 기쁩니다. 위의 버그는 단순히 불가능합니다. 코드가 컴파일되기 전에 수동으로 명확히해야합니다.
토마스 에딩

2
에 상속의 순서를 변경 Bang하는 것은 Bar, Foo또한 문제를 해결하는 것입니다 -하지만 당신의 포인트는, +1 좋은 하나입니다.
Sean Vieira

1
@ThomasEding 여전히 수동으로 명확하게 할 수 있습니다 : Bar.zeta(self).
Tyler Crompton

9

나는 올바른 언어 로 MI와 관련한 실질적인 문제는 없다고 주장 할 것이다 . 핵심은 다이아몬드 구조를 허용하는 것이지만 컴파일러가 규칙에 따라 구현 중 하나를 선택하는 대신 하위 유형이 자체 재정의를 제공하도록 요구합니다.

저는 현재 작업중인 언어 인 구아바 에서이 작업을 수행합니다. 구아바의 한 가지 특징은 특정 수퍼 타입의 메소드 구현을 호출 할 수 있다는 것입니다. 따라서 특별한 구문없이 "상속되어야하는"수퍼 타입 ​​구현을 쉽게 나타낼 수 있습니다.

type Sequence[+A] {
  String toString() {
    return "[" + ... + "]";
  }
}

type Set[+A] {
  String toString() {
    return "{" + ... + "}";
  }
}

type OrderedSet[+A] extends Sequence[A], Set[A] {
  String toString() {
    // This is Guava's syntax for statically invoking instance methods
    return Set.toString(this);
  }
}

우리가 OrderedSet자체를 주지 않으면 toString컴파일 오류가 발생합니다. 놀랍지 않습니다.

MI가 컬렉션에 특히 유용하다는 것을 알았습니다. 예를 들어, 배열, deque 등을 RandomlyEnumerableSequence선언하지 않도록 유형 을 사용하고 싶습니다 getEnumerator.

type Enumerable[+A] {
  Source[A] getEnumerator();
}

type Sequence[+A] extends Enumerable[A] {
  A get(Int index);
}

type RandomlyEnumerableSequence[+A] extends Sequence[A] {
  Source[A] getEnumerator() {
    ...
  }
}

type DynamicArray[A] extends MutableStack[A],
                             RandomlyEnumerableSequence[A] {
  // No need to define getEnumerator.
}

MI가 없다면 RandomAccessEnumerator몇 가지 컬렉션을 사용할 수는 있지만 간단한 getEnumerator방법 을 작성하면 여전히 상용구가 추가됩니다.

마찬가지로, MI는 표준 구현 상속에 유용합니다 equals, hashCode그리고 toString모음을.


1
@AndyzSmith, 요점을 알지만 모든 상속 충돌이 실제 의미 론적 문제는 아닙니다. 내 예를 고려하십시오-어떤 유형도 toString의 행동 에 대해 약속 하지 않으므로 그 행동을 재정의한다고해서 대체 원칙을 위반하지는 않습니다. 때로는 동작이 동일하지만 알고리즘이 다른 메소드, 특히 컬렉션과 충돌하는 메소드간에 "충돌"이 발생하는 경우가 있습니다.
다니엘 루바 로프

1
@Asmageddon은 동의했지만 내 예제에는 기능 만 포함되어 있습니다. 믹스 인의 장점은 무엇입니까? 적어도 스칼라에서 특성은 본질적으로 MI를 허용하는 추상 클래스 일뿐입니다. 이는 여전히 정체성에 사용되어 다이아몬드 문제를 일으킬 수 있습니다. 믹스 인에 더 흥미로운 차이점이있는 다른 언어가 있습니까?
Daniel Lubarov

1
기술적으로 추상 클래스는 인터페이스로 사용할 수 있습니다. 구조체 자체가 "사용 팁"이라는 사실만으로 이익을 얻을 수 있습니다. 즉, 코드를 볼 때 무엇을 기대해야하는지 알 수 있습니다. 즉, 현재 고유 한 OOP 모델이없는 Lua로 코딩하고 있습니다. 기존 클래스 시스템은 일반적으로 테이블을 믹스 인으로 포함 할 수 있으며 코딩하는 시스템은 클래스를 믹스 인으로 사용합니다. 유일한 차이점은 ID에 대한 (단일) 상속, 기능에 대한 믹스 인 (ID 정보가 없음) 및 추가 검사에 대한 인터페이스 만 사용할 수 있다는 것입니다. 어쩌면 그것은 더 나아지지는 않지만 나에게는 의미가 있습니다.
Llamageddon

2
왜 전화 Ordered extends Set and SequenceDiamond합니까? 단지 조인입니다. hat정점 이 부족합니다 . 왜 다이아몬드라고 부릅니까? 나는 여기에서 물었다 . 그러나 그것은 금기의 질문 인 것처럼 보인다. 이 삼각 구조를 Join이 아니라 Diamond라고 부르는 것을 어떻게 알았습니까?
Val

1
@RecognizeEvilasWaste 언어에는 Top을 선언 하는 암시 적 유형이 toString있으므로 실제로 다이아몬드 구조가 존재합니다. 하지만 다이아몬드 구조가없는 "결합"은 비슷한 문제를 야기하며 대부분의 언어는 두 경우를 모두 같은 방식으로 처리합니다.
다니엘 루바 로프

7

상속은 여러 가지 또는 다른 방식으로 중요하지 않습니다. 유형이 다른 두 객체가 대체 가능하면 상속으로 연결되지 않은 경우에도 중요합니다.

연결된 목록과 문자열은 공통점이 거의 없으며 상속을 통해 연결할 필요는 없지만 length함수를 사용하여 둘 중 하나의 요소 수를 얻을 수 있으면 유용합니다 .

상속은 반복되는 코드 구현을 피하는 요령입니다. 상속이 작업을 절약하고 다중 상속이 단일 상속에 비해 훨씬 더 많은 작업을 절약하면 이것이 필요한 모든 정당화입니다.

일부 언어는 다중 상속을 잘 구현하지 못하고 해당 언어의 실무자에게 다중 상속이 의미하는 것으로 생각합니다. C ++ 프로그래머에게 다중 상속을 언급하고 염두에 두어야 할 것은 클래스가 두 개의 서로 다른 상속 경로를 통해 두 개의 기본 복사본으로 끝나고 virtual기본 클래스에서 사용할지 여부 와 소멸자가 호출되는 방법에 대한 혼란 , 등등.

많은 언어에서 클래스의 상속은 심볼의 상속과 관련이 있습니다. 클래스 B에서 클래스 D를 파생시킬 때 유형 관계를 생성 할뿐만 아니라 이러한 클래스가 어휘 네임 스페이스로도 사용되므로 B 네임 스페이스에서 D 네임 스페이스로 심볼을 가져 오는 작업을 처리하게됩니다. 타입 B와 D 자체에서 일어나는 일의 의미론. 다중 상속은 따라서 심볼 충돌 문제를 일으킨다. 메소드 를 "갖고"있는 card_deckand 로부터 상속을 받으면 결과 객체에 어떤 의미가 있습니까? 이 문제가없는 객체 시스템은 Common Lisp의 시스템입니다. 우연히도 다중 상속은 Lisp 프로그램에서 사용됩니다.graphicdrawdraw

잘못 구현되고 불편한 것 (예 : 다중 상속) 미워 해야 합니다.


2
list와 string 컨테이너의 length () 메소드를 사용한 예제는이 두 가지가 완전히 다른 일을하기 때문에 좋지 않습니다. 상속의 목적은 코드 반복을 줄이는 것이 아닙니다. 코드 반복을 줄이려면 공통 부분을 새로운 클래스로 리팩토링하십시오.
BЈовић

1
@ BЈовић 두 사람은 전혀 "완전히"다른 일을하고 있지 않습니다.
Kaz

1
stringlist를 나누면 많은 반복을 볼 수 있습니다. 상속을 사용하여 이러한 일반적인 메소드를 구현하는 것은 단순히 잘못된 것입니다.
BЈовић

1
@ BЈовић 대답에서 문자열과 목록은 상속에 의해 연결되어야한다고 말하지 않았습니다. 정반대. length오퍼레이션 에서리스트 또는 문자열을 대체 할 수있는 동작은 상속 개념과는 무관하게 유용합니다 (이 경우에는 도움이되지 않습니다. 구현을 공유하려고 시도해도 어떤 것도 달성 할 수 없습니다) length함수 의 두 가지 방법 ). 그럼에도 불구하고 추상 상속이있을 수 있습니다 : 예를 들어 list와 string 모두 시퀀스 유형입니다 (그러나 구현을 제공하지는 않습니다).
Kaz

2
또한 상속 파트를 공통 클래스, 즉 기본 클래스로 리팩토링하는 방법입니다.
Kaz

1

내가 알 수있는 한, 문제의 일부 (디자인을 조금 더 이해하기 어렵게 만드는 것 (그러나 코딩하기는 쉽지만))는 컴파일러가 클래스 데이터를위한 충분한 공간을 절약하여 엄청난 양의 공간을 허용한다는 것입니다 다음과 같은 경우 메모리 낭비 :

(내 예제는 최고는 아니지만 동일한 목적을 위해 여러 메모리 공간에 대한 요점을 얻으려고 노력하십시오.

반려견이 개와 애완 동물로부터 뻗어 나오는 DDD를 고려하면, 개종은 dietKg라는 이름으로 먹어야하는 음식의 양 (정수)을 나타내는 변수를 가지고 있지만, 애완 동물은 일반적으로 같은 목적으로 그 목적을위한 또 다른 변수를 가지고 있습니다 name (다른 변수 이름을 설정하지 않으면 추가 코드를 코드화 할 것입니다. 이는 피해야 할 초기 문제였으며, 부스 변수의 무결성을 처리하고 유지하기 위해) 정확한 두 개의 메모리 공간이 있습니다. 같은 목적으로 이것을 피하려면 동일한 네임 스페이스에서 해당 이름을 인식하고 해당 데이터에 단일 메모리 공간을 할당하기 위해 컴파일러를 수정해야합니다. 불행히도 컴파일 시간을 결정할 수는 없습니다.

물론 그러한 변수에 이미 다른 곳에 정의 된 공간이있을 수 있도록 언어를 설계 할 수 있지만, 결국 프로그래머는이 변수가 참조하는 메모리 공간이 어디인지 (그리고 추가 코드) 지정해야합니다.

이 모든 것에 대해 정말로 열심히 생각하는 사람들을 믿으십시오.하지만 당신의 친절한 전망은 패러다임을 바꾸는 것입니다.)이 문제를 해결할 수는 없습니다. (하지만 많은 가정과 다단계 컴파일러를 구현해야하고 실제로는 복잡한 것이 필요합니다.) "this"(다중 상속)를 수행 할 수있는 자체 컴파일러를위한 프로젝트를 시작하는 경우 아직 존재하지 않는다고 말하고 싶습니다. 팀에 합류하게되어 기쁩니다.


1
컴파일러는 어떤 시점에서 다른 부모 클래스와 동일한 이름의 변수를 결합해도 안전하다고 가정 할 수 있습니까? caninus.diet와 pet.diet가 실제로 동일한 목적을 달성한다는 보장은 무엇입니까? 같은 이름의 함수 / 메서드로 무엇을 하시겠습니까?
Mr.Mindor 2011

hahaha 나는 당신에게 @ Mr.Mindor에 전적으로 동의합니다. 내 대답에서 정확히 말하고있는 것입니다. 그런 접근에 접근하기 위해 내 마음에 오는 유일한 방법은 프로토 타입 지향 프로그래밍이지만 불가능하지는 않습니다. 이것이 바로 컴파일 시간 동안 많은 가정이 이루어져야하고 프로그래머가 일부 "데이터"/ 사양을 작성해야한다고 말한 이유입니다 (그러나 더 많은 코딩이 필요하지만 원래 문제였습니다)
Ordiel

-1

지금까지 어느 정도의 프로그래밍 작업이 다른 프로그래밍 작업과 완전히 다른지, 그리고 사용 된 언어와 패턴이 문제 영역에 맞게 조정 된 경우 얼마나 도움이되는지 전혀 제게 실제로 발생하지 않았습니다.

혼자서 또는 주로 작성한 코드에서 작업하는 경우 1 년 동안 일한 인도의 40 명의 코드베이스를 과도기적 인 도움없이 사용자에게 넘겨주는 코드베이스를 물려받는 것과는 완전히 다른 문제 공간입니다.

꿈의 회사에서 방금 고용 한 후 그러한 코드 기반을 상속했다고 상상해보십시오. 또한 컨설턴트가 상속과 다중 상속에 대해 배우고 (따라서 마음에 들지 않았다고) 상상해보십시오 ... 작업중 인 것을 상상할 수 있습니까?

코드를 상속받을 때 가장 중요한 기능은 이해할 수 있고 조각이 분리되어 독립적으로 작업 할 수 있다는 것입니다. 다중 상속과 같은 코드 구조를 처음 작성할 때 약간의 복제가 줄어들고 당시의 논리적 분위기에 맞는 것처럼 보일 수 있지만 다음 사람은 풀어야 할 것이 더 많습니다.

코드의 모든 상호 연결로 인해 여러 상속을 통해 조각을 독립적으로 이해하고 수정하기가 더 어려워집니다.

팀의 일원으로 일할 때 중복 로직을 전혀 제공하지 않는 가장 간단한 코드를 대상으로하고 싶습니다 (DRY가 실제로 의미하는 바는 2 번에 코드를 변경하지 않아도된다는 것만으로 많이 입력해서는 안됩니다) 문제를 해결할 곳!)

DRY 코드를 다중 상속하는 것보다 DRY 코드를 얻는 간단한 방법이 있으므로 언어로 코드를 포함하면 자신과 같은 수준의 이해력이없는 다른 사람이 삽입 한 문제 만 열 수 있습니다. 언어가 코드를 DRY로 유지하는 간단하고 복잡한 방법을 제공 할 수없는 경우에만 유혹입니다.


또한 컨설턴트가 배우고 있다고 상상해보십시오. 컨설턴트가 인터페이스 기반 DI 및 IoC에 열중하여 비정형 적으로 혼란스러운 혼란을 초래하는 많은 비 MI 언어 (예 : .net)를 보았습니다. MI는 이것을 악화시키지 않으며 아마도 더 나아질 것입니다.
gbjbaanb

@gbjbaanb 맞습니다. 번에 타지 않은 사람이 기능을 엉망으로 만드는 것은 쉽습니다. 사실 가장 잘 사용하는 방법을보기 위해 엉망으로 만드는 기능을 배우는 것이 거의 필요합니다. MI가 내가 보지 못한 DI / IoC에 더 많은 힘을 가하지 않는다고 말하고 있기 때문에 궁금합니다. 당신이 모든 크 래피 인터페이스 / DI로 얻은 컨설턴트 코드는 또한 전체적으로 다 대다 상속 트리를 사용하는 것이 좋지만 MI에 대한 경험이 없을 수 있습니다. DI / IoC를 MI로 교체 할 때 읽을 수있는 페이지가 있습니까?
Bill K

-3

다중 상속에 대한 가장 큰 논란은 그것을 (*) 엄격하게 제한하지만, 그러한 제한 없이는 제공 및 / 또는 보유 할 수없는 프레임 워크에서 일부 유용한 능력을 제공하고 일부 유용한 공리를 유지할 수 있다는 것입니다. 그들 중 :

  • 여러 개의 개별적으로 컴파일 된 모듈을 가질 수있는 기능에는 다른 모듈의 클래스에서 상속되는 클래스가 포함되며 해당 기본 클래스에서 상속 된 모든 모듈을 다시 컴파일하지 않고도 기본 클래스가 포함 된 모듈을 다시 컴파일 할 수 있습니다.

  • 파생 된 형식을 다시 구현할 필요없이 형식이 부모 클래스에서 멤버 구현을 상속하는 기능

  • 모든 객체 인스턴스가 자체 또는 기본 유형으로 직접 업 캐스트 또는 다운 캐스트 될 수 있으며, 이러한 업 캐스트 및 다운 캐스트는 조합 또는 순서에 따라 항상 ID를 유지한다는 원칙

  • 파생 클래스가 재정의되고 기본 클래스 멤버에 연결되면 기본 멤버는 연결 코드에 의해 직접 호출되며 다른 곳에서는 호출되지 않습니다.

(*) 일반적으로 다중 상속을 지원하는 형식은 클래스가 아닌 "인터페이스"로 선언해야하며 인터페이스에서 일반 클래스가 수행 할 수있는 모든 작업을 수행 할 수 없습니다.

일반화 된 다중 상속을 허용하려면 다른 것이 필요합니다. X와 Y가 모두 B에서 상속되면 둘 다 동일한 멤버 M을 재정의하고 기본 구현에 체인을 연결하고 D가 X와 Y에서 상속하지만 M을 재정의하지 않는 경우 D 유형의 인스턴스 q가 주어지면 (( B) q) .M ()은? 이러한 캐스트를 허용하지 않으면 모든 오브젝트가 모든 기본 유형으로 캐스트 될 수 있다고 말하는 공리를 위반하지만 캐스트 및 멤버 호출의 모든 가능한 동작은 메소드 체인과 관련된 공리를 위반합니다. 클래스는 컴파일 된 특정 버전의 기본 클래스와 결합하여 클래스를로드 할 것을 요구할 수 있지만 종종 어색합니다. 런타임에서 둘 이상의 경로로 조상에 도달 할 수있는 모든 유형의로드를 거부하면 작동 가능할 수 있습니다. 그러나 다중 상속의 유용성을 크게 제한 할 것입니다. 충돌이없는 경우에만 공유 상속 경로를 허용하면 이전 버전의 X가 이전 또는 새로운 Y와 호환되고 이전 Y가 이전 또는 새로운 X와 호환되는 상황이 발생하지만 새 X 및 새 Y는 그 자체로도 중대한 변화가 될 것으로 예상되는 어떤 것도하지 않았더라도 호환 가능해야합니다.

일부 언어와 프레임 워크는 MI로부터 얻은 것이 그것을 허용하기 위해 포기해야하는 것보다 더 중요하다는 이론에 따라 다중 상속을 허용합니다. 그러나 MI의 비용은 상당하지만 많은 경우 인터페이스가 적은 비용으로 MI의 이점 중 90 %를 제공합니다.


다운 투표자들은 의견을 주장할까요? 내 대답은 다중 상속을 허용하지 않음으로써 얻을 수있는 의미상의 이점과 관련하여 다른 언어로 존재하지 않는 정보를 제공한다고 생각합니다. 다중 상속을 허용하면 다른 유용한 것들을 포기해야한다는 사실이 MI를 반대하기 위해 다중 상속보다 다른 것들을 중요하게 생각하는 사람에게는 좋은 이유가 될 것입니다.
supercat

2
MI와 관련된 문제는 단일 상속에 사용하는 것과 동일한 기술을 사용하여 쉽게 해결하거나 피할 수 있습니다. 언급 한 기능 / 축은 두 번째 클래스를 제외하고 본질적으로 MI에 의해 방해받지 않습니다 ... 그리고 같은 방법에 대해 다른 행동을 제공하는 두 클래스에서 상속하는 경우 어쨌든 그 모호성을 해결해야합니다. 그러나 당신이 그렇게해야한다면 이미 상속을 잘못 사용하고있을 것입니다.
cHao

@cHao : 파생 클래스가 부모 메서드를 재정의해야 하고 그것들과 연결되지 않아야하는 경우 (인터페이스와 같은 규칙) 문제를 피할 수 있지만 이는 매우 심각한 제한입니다. 하나는 패턴을 사용하는 경우 FirstDerivedFoo'의의 재정이 Bar보호 된 비가 상에 체인 아무것도하지 않습니다를 FirstDerivedFoo_Bar하고, SecondDerivedFoo에 재정의 체인의 보호가 아닌 가상 SecondDerivedBar및 기본 방법에 액세스하려고 코드는 그 보호가 아닌 가상 메소드를 사용하는 경우, 그 실행 가능한 접근 방법 일 수 있습니다.
supercat

... (구문을 빼앗아 base.VirtualMethod가고 있지만) 그러한 접근 방식을 촉진하기 위해 어떤 언어 지원도 모릅니다. "한 줄짜리"가상 방법을 요구하지 않고 하나의 코드 블록을 비가 상 보호 방법과 동일한 서명을 가진 가상 방법 모두에 연결하는 깔끔한 방법이 있었으면 좋겠다. 소스 코드에서).
supercat

MI에는 클래스가 기본 메소드를 호출하지 않는 고유 한 요구 사항이 없으며 특별한 이유가 필요하지 않습니다. 문제가 SI에도 영향을 미친다는 점을 감안하면 MI를 비난하는 것은 조금 이상합니다.
cHao
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.