多重継承が嫌われる「本当の」理由はありますか?


123

私は常に、言語で複数の継承をサポートするというアイデアが好きでした。ほとんどの場合、意図的に無視されますが、想定される「置換」はインターフェースです。インターフェースは、多重継承が行うものと同じ基盤をすべてカバーするものではなく、この制限により、定型コードが増える場合があります。

私がこれまでに聞いた唯一の基本的な理由は基本クラスのダイヤモンドの問題です。私はそれを受け入れることができません。私にとっては、「まあ、それを台無しにすることは可能だから、それは自動的に悪い考えだ」というようなひどい結果になります。ただし、プログラミング言語で何かを台無しにすることができます、と私は何を意味します。少なくとももっと徹底的な説明がなければ、私はこれを真剣に受け止めることはできません。

この問題を知っているだけで、戦いの90%になります。さらに、私は何年か前に「エンベロープ」アルゴリズムまたはそのようなものを含む汎用的な回避策について聞いたことがあると思います(これは鐘を鳴らしますか?)。

ダイヤモンドの問題に関して、私が考えることができる唯一の潜在的に本物の問題は、サードパーティのライブラリを使用しようとしており、そのライブラリ内の2つの一見無関係なクラスが共通の基本クラスを持っていることを見ることができない場合ですが、ドキュメンテーション、単純な言語機能では、たとえば、実際にコンパイルする前に、ダイヤモンドを作成する意図を明確に宣言する必要があります。このような機能を使用すると、ダイヤモンドの作成は意図的、無謀、またはこの落とし穴に気付かないためです。

すべてが言われているように...あらゆるあり、実際のほとんどの人は多重継承を憎む理由は、またはそれが良いよりも害の原因とヒステリーのすべてのちょうど束でありますか?ここにないものがありますか?ありがとうございました。

車はWheeledVehicleを拡張し、KIASpectraはCarとElectronicを拡張し、KIASpectraはラジオを含みます。KIASpectraにElectronicが含まれないのはなぜですか?

  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に関連するこれらの機能のいくつかがどのように存在するか想像できます。

そこには解釈の余地があるので、その例に落ち着くかどうかはわかりませんでした。また、VehicleとFlyingObjectを拡張するPlaneや、AnimalとFlyingObjectを拡張するBirdの観点、またはより純粋な例の観点から考えることもできます。


24
また、構成よりも継承を促進します...(逆の場合)
ラチェットフリーク

34
「あなたはそれを台無しにすることができます」は、機能を削除する完全に正当な理由です。現代の言語設計はその点で多少分裂していますが、一般的に言語を「制限からの力」と「柔軟性からの力」に分類できます。どちらも「正しい」のではなく、どちらも強い点があるとは思いません。MIは、悪のために頻繁に使用されるものの1つであり、したがって、制限的な言語はそれを削除します。柔軟なものはそうではありません。なぜなら、「ほとんどの場合」は「文字通り常に」ではないからです。とはいえ、ミックスイン/トレイトは一般的な場合のより良い解決策だと思います。
-Phoshi

8
複数の言語には、インターフェースを超えた多重継承のより安全な代替手段があります。ScalaをチェックしてくださいTraits-オプションの実装を備えたインターフェースのように振る舞いますが、ダイヤモンドの問題のような問題の発生を防ぐのに役立ついくつかの制限があります。
KChaloux

5
また、この以前は、閉じた質問を参照してください:programmers.stackexchange.com/questions/122480/...
ドク・ブラウン

19
KiaSpectraありません。それ電子機器を持っており、(拡張するかもしれません...) ElectronicElectronicCarCar
ブライアンS

回答:


68

多くの場合、人々は継承を使用してクラスに特性を提供します。たとえば、ペガサスを考えてください。多重継承を使用すると、鳥を翼のある動物として分類したため、ペガサスが馬と鳥を拡張すると言いたくなるかもしれません。

ただし、鳥にはペガシにはない他の特性があります。たとえば、鳥は卵を産み、ペガシは出生します。継承が共有特性を渡す唯一の手段である場合、ペガサスから産卵特性を除外する方法はありません。

一部の言語では、言語内で特性を明示的な構成にすることを選択しています。他の言語は、MIを言語から削除することで、その方向に優しく案内します。いずれにせよ、「これを適切に行うには本当にMIが必要だ」と思った1つのケースは考えられません。

また、本当に継承とは何かを議論しましょう。クラスから継承する場合、そのクラスに依存しますが、クラスがサポートするコントラクト(暗黙的および明示的の両方)もサポートする必要があります。

長方形から継承する正方形の典型的な例を見てみましょう。四角形は、長さと幅のプロパティ、およびgetPerimeterとgetAreaメソッドを公開します。正方形は長さと幅をオーバーライドするため、一方が設定されると、他方はgetPerimeterと一致するように設定され、getAreaは同じように機能します(周囲は2 * length + 2 * width、周囲はlength * width)。

この実装を四角形の代わりに四角形に置き換えると壊れる単一のテストケースがあります。

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

単一の継承チェーンで物事を正しくするのは十分に困難です。別のミックスを追加すると、さらに悪化します。


ミシガン州のペガサスで述べた落とし穴と、長方形/正方形の関係は、両方ともクラスの経験の浅い設計の結果です。基本的に、多重継承を回避することは、開発者が足を踏み入れないようにする開発者を支援する方法です。すべての設計原則と同様に、それらに基づいた規律とトレーニングを行うことにより、それらを打ち破るのがいつよいかを時間内に発見できます。Dreyfus Model of Skill Acquisitionのエキスパートレベルをご覧ください。あなたの本質的な知識は、格言/原則への依存を超えています。ルールが適用されないときに「感じる」ことができます。

そして、私はMIが眉をひそめている理由の「現実の世界」の例でいくぶんごまかしたことに同意します。

UIフレームワークを見てみましょう。具体的には、最初は単純に他の2つのウィジェットの組み合わせのように見えるかもしれないいくつかのウィジェットを見てみましょう。ComboBoxのように。ComboBoxは、サポートするDropDownListを持つTextBoxです。つまり、値を入力することも、事前に定義された値のリストから選択することもできます。素朴なアプローチは、TextBoxとDropDownListからComboBoxを継承することです。

ただし、Textboxは、ユーザーが入力した値から値を導き出します。DDLは、ユーザーが選択したものからその値を取得します。誰が先例を取りますか?DDLは、元の値リストにない入力を検証および拒否するように設計されている場合があります。そのロジックをオーバーライドしますか?つまり、継承者がオーバーライドするための内部ロジックを公開する必要があります。さらに悪いことに、サブクラスをサポートするためにのみ存在する基本クラスにロジックを追加します(依存関係反転の原則に違反します)。

MIを回避すると、この落とし穴を完全に回避できます。また、UIウィジェットの一般的で再利用可能な特性を抽出して、必要に応じて適用できるようにする場合があります。これの優れた例は、WPFのフレームワーク要素が、別のフレームワーク要素が親フレームワーク要素から継承せずに使用できるプロパティを提供できるWPF添付プロパティです。

たとえば、グリッドはWPFのレイアウトパネルであり、グリッドの配置で子要素を配置する場所を指定するColumnおよびRow添付プロパティがあります。添付プロパティがない場合、グリッド内にボタンを配置する場合、ボタンはグリッドから派生する必要があるため、列および行のプロパティにアクセスできます。

開発者はこの概念をさらに進め、動作をコンポーネント化する方法として添付プロパティを使用しました(たとえば、WPFにDataGridが含まれる前に記述された添付プロパティを使用して並べ替え可能なGridViewを作成することに関する投稿です)。このアプローチは、Attached Behaviorsと呼ばれるXAMLデザインパターンとして認識されています。

これが、多重継承が一般的に眉をひそめている理由についてもう少し洞察を提供してくれることを願っています。


14
「これを適切に行うには本当にMIが必要な人」-例については私の答えを参照してください。一言で言えば、is-a関係を満たす直交概念はMIにとって意味があります。それらは非常にまれですが、存在します。
MrFox

13
男、私はその正方形の長方形の例が嫌いです。可変性を必要としない、より多くの照明の例があります。
asmeurer

9
「実例を提供する」という答えは、架空の生き物について議論することでした。優秀な皮肉+1
ジャスマン

3
つまり、継承は悪いため、多重継承は悪いと言っているのです(例はすべて単一のインターフェースの継承です)。したがって、この答えだけに基づいて、言語は継承とインターフェースを取り除くか、複数の継承を持つ必要があります。
ctrl-alt-delor

12
私はこれらの例が本当にうまくいくとは思いません...ペガサスは鳥であり馬であると言う人はいません...それはWingedAnimalとHoofedAnimalだと言います。正方形と長方形の例は、主張された定義に基づいて考えるとは異なる動作をするオブジェクトに自分自身を見つけるので、もう少し理にかなっています。ただし、この状況はプログラマーがこれをキャッチしなかったために発生する障害だと思います(実際、これが問題であることが判明した場合)。
リチャード

60

ここにないものはありますか?

多重継承を許可すると、関数のオーバーロードと仮想ディスパッチに関するルールが明らかにトリッキーになり、オブジェクトレイアウトに関する言語の実装も難しくなります。これらは言語設計者/実装者にかなりの影響を与え、すでに高い水準を引き上げて、言語を完成させ、安定させ、採用します。

私が見た(そして時々した)別の一般的な議論は、2つ以上の基本クラスを持つことで、オブジェクトは常に単一責任原則に違反するということです。2つ以上の基本クラスは、(違反の原因となる)独自の責任を持つ優れた自己完結型クラスであるか、相互に連携して単一のまとまった責任を果たす部分型または抽象型です。

この場合、次の3つのシナリオがあります。

  1. AはBについて何も知らない-素晴らしい。幸運だったので、クラスを組み合わせることができた。
  2. AはBについて知っています-なぜAはBから継承しなかったのですか?
  3. AとBはお互いを知っている-なぜあなたは1つのクラスを作らなかったのですか?これらのものを非常に結合しているが、部分的に交換可能にすることからどのような利点がありますか

個人的には、多重継承には悪いラップがあり、よくできた特性スタイル構成のシステムは本当に強力で便利だと思います... C ++のような言語ではお勧めできません。

[編集]あなたの例に関して、それはばかげています。起亜電子機器を持っています。それは持っているエンジンを。同様に、電子機器は電源があり、これはたまたまカーバッテリーになっています。継承はもちろんのこと、複数の継承には場所がありません。


8
もう1つの興味深い質問は、基本クラスとその基本クラスをどのように初期化するかです。カプセル化>継承。
jozefg

「特徴的なスタイルの構成のよくできたシステムは本当に強力で便利です...」 -これらはmixinとして知られており、強力で便利です。MIを使用してミックスインを実装できますが、ミックスインには多重継承は必要ありません。 一部の言語は、MIなしで本質的にミックスインをサポートします。
BlueRaja-ダニーPflughoeft

特にJavaについては、複数のインターフェイスとINVOKEINTERFACEが既にあるため、MIはパフォーマンスに明らかな影響を与えません。
ダニエルルバロフ

テタスティン、この例は悪いものであり、彼の質問には役立たなかったことに同意します。継承は形容詞(電子)ではなく、名詞(車両)です。
リチャード

29

それが許可されない唯一の理由は、人々が足で自分自身を撃ちやすくするためです。

この種の議論で通常行われるのは、ツールを持っていることの柔軟性が、足から撃たないことの安全性よりも重要であるかどうかに関する議論です。プログラミングの他のほとんどのものと同様に、答えはコンテキストに依存するため、その引数に対する明確な正解はありません。

開発者がMIに慣れていて、MIが自分のやっていることの文脈で理にかなっている場合、それをサポートしていない言語では見逃してしまうでしょう。同時に、チームがそれに慣れていない場合、または実際にそれを必要とせず、人々が「できるから」という理由でそれを使用する場合、それは非生産的です。

しかし、いや、多重継承が悪い考えであることを証明する、完全に説得力のある絶対的に正しい議論は存在しません。

編集

この質問への回答は全会一致のようです。悪魔の擁護者であるために、多重継承の良い例を提供します。多重継承を行わないと、ハッキングにつながります。

資本市場アプリケーションを設計しているとします。証券のデータモデルが必要です。一部の証券は株式商品(株式、不動産投資信託など)であり、その他は負債(債券、社債)であり、その他はデリバティブ(オプション、先物)です。したがって、MIを回避する場合は、非常に明確で単純な継承ツリーを作成します。株式は株式を引き継ぎ、債券は負債を引き継ぎます。これまでのところ素晴らしいですが、デリバティブはどうですか?それらは、エクイティのような製品またはデビットのような製品に基づいていますか?継承ツリーをさらに分岐させると思います。いくつかのデリバティブは、エクイティ商品、デット商品、またはどちらにも基づいていないことに留意してください。したがって、継承ツリーは複雑になっています。次に、ビジネスアナリストが来て、インデックス化された証券(インデックスオプション、将来のインデックスオプション)をサポートするようになりました。そして、これらのことは、株式、負債、またはデリバティブに基づいている場合があります。これは面倒です!私のインデックスの将来のオプションは、Equity-> Stock-> Option-> Indexを導き出しますか?なぜ株式->株式->インデックス->オプションではありませんか?ある日、コード内で両方を見つけたらどうなりますか(これが起こった;実話)。

ここでの問題は、これらの基本型が、一方から他方を自然に導出しない順列に混在できることです。定義されているオブジェクトがある関係ので、組成物は全く意味がありません。ここでは、多重継承(またはmixinの同様の概念)が唯一の論理表現です。

この問題の実際の解決策は、多重継承を使用して、株式、負債、デリバティブ、インデックスタイプを定義および混合し、データモデルを作成することです。これにより、両方の意味を持つオブジェクトが作成され、コードの再利用が容易になります。


27
私は金融で働いてきました。金融ソフトウェアでオブジェクト指向よりも関数型プログラミングが好まれる理由があります。そして、あなたはそれを指摘しました。MIはこの問題を修正せず、悪化させます。信じてください。金融クライアントに対処するときに「これは…と同じようなものだ」と何度も聞いたことはありませんが、それ以外は私の存在の悩みの種になります。
マイケルブラウン

8
これは私へのインターフェイスを持つ非常に解けるようだ...実際には、それはそうです、より良い何よりもインターフェースに適しています。 Equityそして、Debt両方とも実装しISecurityます。 プロパティDerivativeがありISecurityます。それ自体がISecurity適切な場合もあります(私は金融を知りません)。 IndexedSecuritiesここでも、基になることが許可されている型に適用されるインターフェイスプロパティが含まれています。それらがすべてISecurityである場合、それらはすべてISecurityプロパティを持ち、任意にネストすることができます
...-ボブソン

11
@Bobsonの問題は、各実装者のインターフェイスを再実装する必要があるという点にあります。多くの場合、それはまったく同じです。委任/構成を使用できますが、プライベートメンバーへのアクセスが失われます。また、Javaにはデリゲート/ラムダがないため、文字通りそれを行う良い方法はありません。おそらくAspect4Jのようなアスペクトツールを除いて
マイケル・ブラウン

10
@Bobsonマイクブラウンが言ったことを正確に。はい、インターフェイスを使用してソリューションを設計できますが、それは不格好です。インターフェイスを使用するあなたの直感は非常に正しいですが、ミックスイン/多重継承への隠された欲求です:)。
MrFox

11
これは、OOプログラマーが継承の観点から考えることを好み、有効なOOアプローチとして委任を受け入れない場合が多いという奇妙な点に触れています。これは通常、よりスリムで、保守可能で拡張可能なソリューションをもたらします。金融とヘルスケアで働いたことがあるので、特に税法と健康法が年々変更され、昨年のオブジェクトの定義がこの年には無効であるため、MIは管理できない混乱を引き起こすことがあります(まだどちらの機能も実行する必要があります、交換可能である必要があります)。コンポジションは、テストしやすい無駄のないコードを生成し、時間の経過とともにコストを削減します。
ショーンウィルソン

11

ここでの他の答えは、主に理論になっているようです。そこで、具体的にPythonの例を示します。簡略化したものですが、実際に真っ向から突き当たりましたが、かなりのリファクタリングが必要でした。

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申し訳ありませんが、その部分は決して気にしません。Pythonはダイヤモンド型の継承によって引き起こされる別の問題だった-私は、「ダイヤモンドの問題は、」通常のことをいう問題misremembered super()
Izkata

5
C ++のMIの設計がはるかに優れていることを嬉しく思います。上記のバグは単に不可能です。コードをコンパイルする前に、手動で明確にする必要があります。
トーマスエディング

2
継承の順序を変更BangするBar, Fooことでも問題は解決しますが、ポイントは+1です。
ショーンビエイラ

1
@ThomasEdingまだ手動で明確にすることができます:Bar.zeta(self)
タイラークロンプトン14

9

私は、適切な言語でMI 本当の問題がないと主張するでしょう。重要なのは、ダイヤモンド構造を許可することですが、コンパイラがルールに基づいて実装の1つを選択する代わりに、サブタイプが独自のオーバーライドを提供することを要求します。

これは、現在取り組んでいる言語であるグアバで行います。Guavaの特徴の1つは、メソッドの特定のスーパータイプの実装を呼び出すことができることです。したがって、特別な構文を使用せずに、どのスーパータイプの実装を「継承」する必要があるかを簡単に示すことができます。

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はコレクションで特に役立つことがわかりました。たとえば、配列や両端キューなどの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は、標準実装の継承のために有用であるequalshashCodetoStringコレクションのために。


1
@AndyzSmith、あなたの意見はわかりますが、すべての継承の競合が実際のセマンティック問題ではありません。私の例を考えてみましょう-どのタイプもtoStringの動作について約束していませんので、その動作をオーバーライドしても置換の原則に違反することはありません。また、特にコレクションでは、動作が同じでアルゴリズムが異なるメソッド間で「競合」が発生する場合があります。
ダニエルルバロフ

1
@Asmageddonは同意しました。私の例は機能のみに関係しています。ミックスインの利点は何ですか?少なくともScalaでは、トレイトは基本的にMIを許可する抽象クラスにすぎません。これらは依然としてアイデンティティに使用でき、ダイヤモンドの問題を引き起こす可能性があります。ミックスインにもっと興味深い違いがある他の言語はありますか?
ダニエルルバロフ14年

1
技術的に抽象クラスをインターフェイスとして使用できますが、その利点は、単に構造体自体が「使用のヒント」であるという事実にあります。つまり、コードを見たときに何を期待するかがわかります。そうは言っても、私は現在、固有のOOPモデルを持たないLuaでコーディングしています。既存のクラスシステムでは、通常、テーブルをミックスインとして含めることができます。唯一の違いは、IDには(単一の)継承、機能(ID情報なし)のmixin、および追加のチェックにはインターフェースのみを使用できることです。たぶんそれは何とか良い方法ではありませんが、私には理にかなっています。
ラマゲドン14年

2
なぜOrdered extends Set and Sequencea と呼ぶのDiamondですか?ただの結合です。hat頂点がありません。なぜダイヤモンドと呼ぶのですか?私はここで尋ねましたが、それはタブーの質問のようです。この三角形の構造を、結合ではなくダイヤモンドと呼ぶ必要があることをどのようにして知りましたか?
ヴァル

1
@RecognizeEvilasWasteには、Topを宣言する暗黙の型があるtoStringため、実際にはダイヤモンド構造がありました。しかし、ダイヤモンド構造のない「結合」は同様の問題を引き起こし、ほとんどの言語は両方のケースを同じ方法で処理します。
ダニエルルバロフ

7

継承は、複数であろうとなかろうと、それほど重要ではありません。異なるタイプの2つのオブジェクトが置換可能である場合、継承によってリンクされていなくても、それが重要です。

リンクリストと文字列にはほとんど共通点がなく、継承によってリンクする必要はありませんが、length関数を使用してどちらかの要素の数を取得できると便利です。

継承は、コードの繰り返しの実装を避けるための秘isです。継承が作業を節約し、多重継承が単一の継承と比較してさらに多くの作業を節約する場合、必要なのはそれだけです。

一部の言語は多重継承をあまりうまく実装しておらず、それらの言語の実践者にとって、それが多重継承の意味です。C ++プログラマーに多重継承について言及します。2つの異なる継承パスを介してクラスが2つのベースコピーで終わる場合の問題、およびvirtualベースクラスで使用するかどうか、およびデストラクタの呼び出し方法に関する混乱について考えます。、 等々。

多くの言語では、クラスの継承はシンボルの継承と混同されます。クラスBからクラスDを派生させる場合、型関係を作成するだけでなく、これらのクラスはレキシカルネームスペースとしても機能するため、BネームスペースからDネームスペースへのシンボルのインポートに加えて、タイプBおよびD自体で行われていることのセマンティクス。したがって、多重継承は、シンボルの衝突の問題をもたらします。私たちが継承した場合card_deckgraphic、「持っている」どちらのdraw方法を、それがに何を意味するのdraw結果のオブジェクト?この問題のないオブジェクトシステムは、Common Lispのものです。たぶん偶然ではないが、Lispプログラムでは多重継承が使用される。

実装が不適切で、不都合なもの(多重継承など)嫌いです。


2
リストおよび文字列コンテナのlength()メソッドを使用した例は、これら2つがまったく異なることをしているため、悪いです。継承の目的は、コードの繰り返しを減らすことではありません。コードの繰り返しを減らしたい場合は、共通部分を新しいクラスにリファクタリングします。
BЈовић

1
@BЈовићこの2つは、「完全に」異なることをまったく行っていません。
カズ

1
stringlistを区切ると、多くの繰り返しが見られます。継承を使用してこれらの一般的なメソッドを実装することは、単に間違っています。
BЈовић

1
@BЈовић答えでは、文字列とリストは継承によってリンクされるべきだとは言われていません。まったく逆です。length操作でリストまたは文字列を置換できるという動作は、継承の概念とは無関係に役立ちます(この場合は役に立ちません:実装を共有しようとして何も達成できない可能性がありますlength関数の2つのメソッド)。それにもかかわらず、いくつかの抽象的な継承が存在する可能性があります。たとえば、リストと文字列の両方がシーケンス型です(ただし、実装は提供されません)。
カズ

2
また、継承、パーツを共通クラス、つまり基本クラスにリファクタリングする方法です。
カズ14

1

私の知る限り、問題の一部は(デザインを少し理解しにくくする(それでもコーディングしやすくする)ことに加えて)コンパイラーがクラスデータのために十分なスペースを節約し、これにより膨大な量の次の場合のメモリの浪費:

(私の例は最良ではないかもしれませんが、同じ目的のために複数のメモリ空間についての要点を取得しようとします、それは私の頭に浮かんだ最初のものでした:P)

クラスの犬がイヌとペットから伸びているDDDを考えてみましょう。イヌには、ダイエットKgという名前で食べるべき食物の量を示す変数(整数)があります。名前(別の変数名を設定しない限り、追加のコードをコード化しますが、これは回避したい最初の問題であり、bouth変数の整合性を処理して維持するためです)、正確な2つのメモリスペースがあります同じ目的で、これを回避するには、コンパイラを変更して同じ名前空間でその名前を認識し、そのデータに単一のメモリ空間を割り当てる必要がありますが、残念ながらコンパイル時に決定することはできません。

もちろん、言語変数を設計して、そのような変数が既にどこか別の場所で定義されているかもしれないことを指定することもできますが、最後に、プログラマーはこの変数が参照しているメモリ空間の場所を指定する必要があります(そして再び追加のコード)。

これを実装する人々が本当にこれについて本当に一生懸命に考えていることを信じてください、しかし、あなたが尋ねたのはうれしいです、あなたの親切な見方はパラダイムを変えるものです;)マルチフェーズコンパイラを実装する必要があり、本当に複雑なもの)、まだ存在しないと言っています。「this」(多重継承)を実行できる独自のコンパイラのプロジェクトを開始する場合、私に知らせてくださいチームに参加できてうれしいです。


1
コンパイラはどの時点で、異なる親クラスからの同じ名前の変数を結合しても安全であると想定できますか?caninus.dietとpet.dietが実際に同じ目的を果たしているという保証はありますか?同じ名前の関数/メソッドで何をしますか?
-Mr.Mindor

ハハハ私はあなたに完全に同意します@ Mr.Mindorそれはまさに私の答えで言っていることです、そのアプローチに近づくために私の心に来る唯一の方法はプロトタイプ指向プログラミングですが、私はそれが不可能だとは言いません、それがまさに、コンパイル時に多くの仮定が必要であり、プログラマーによって追加の「データ」/仕様が記述されなければならないと言った理由です(それにもかかわらず、元の問題であったより多くのコーディングです)
Ordiel

-1

かなり長い間、あるプログラミングタスクが他のタスクとまったく異なること、そして使用されている言語とパターンが問題空間に合わせて調整されている場合、それがどれほど役立つかということは、私には本当に思いもしませんでした。

単独で作業している場合、または作成したコードでほとんど孤立している場合、インドの40人からコードベースを継承するのとはまったく別の問題空間になります。

夢の会社に雇われ、そのようなコードベースを継承したと想像してください。さらに、コンサルタントが継承と多重継承について学習している(したがって、それに夢中になっている)ことを想像してください。

コードを継承する際の最も重要な機能は、それが理解可能であり、各部分が分離されているため、独立して作業できることです。多重継承のようなコード構造を最初に作成するときは、少し重複を避けて、当時のあなたの論理的な気分に合うように思えるかもしれませんが、次の人は解くためのより多くのものを持っています。

また、コード内のすべての相互接続により、部分を個別に理解および変更することがより難しくなります。

チームの一員として働いているときは、冗長なロジックをまったく与えない、可能な限り単純なコードをターゲットにしたい(それは、DRYが本当に意味するものであり、2でコードを変更する必要がないというだけではなく、問題を解決する場所!)

DRYコードを実現するには、複数の継承よりも簡単な方法があります。そのため、言語に含めると、あなたと同じレベルの理解が得られない可能性のある他の人が挿入した問題のみを解決できます。あなたの言語があなたのコードを乾いた状態に保つためのシンプルな/それほど複雑でない方法をあなたに提供することができない場合にのみ魅力的です。


さらに、コンサルタントが学んでいたことを想像してください -私は、コンサルタントがインターフェイスベースのDIとIoCに夢中になり、全体を不可解に複雑な混乱にする非常に多くの非MI言語(例えば.net)を見てきました。MIはこれを悪化させませんが、おそらく改善します。
gbjbaanb

@gbjbaanbその通りです。火傷していない人でも簡単に機能を台無しにすることができます。実際、機能を使いこなすために台無しにする機能を学ぶための要件はほとんどありません。あなたがMIを持っていないと私が見たことがないより多くのDI / IoCを強制すると言っているように見えるので、私はちょっと不思議です-私はあなたがすべてのくだらないインターフェース/ DIで得たコンサルタントコードがそうであったとは思わないでしょうクレイジーな多対多の継承ツリーを持つことも良いですが、それはMIの経験不足かもしれません。DI / IoCをMIに置き換える際に読むことができるページはありますか?
ビルK

-3

多重継承に対する最大の議論は、いくつかの有用な能力を提供でき、いくつかの有用な公理は、それを厳しく制限するフレームワークで保持できるが(*)、そのような制限なしでは提供および/または保持できないことです。その中で:

  • 複数の個別にコンパイルされたモジュールを使用する機能には、他のモジュールのクラスから継承するクラスが含まれ、基本クラスを継承するすべてのモジュールを再コンパイルすることなく、基本クラスを含むモジュールを再コンパイルします。

  • 型が親クラスからメンバー実装を継承する機能。派生型は再実装する必要がありません

  • 任意のオブジェクトインスタンスがそれ自体またはそのベースタイプのいずれかに直接アップキャストまたはダウンキャストされる公理、およびそのようなアップキャストとダウンキャストは、任意の組み合わせまたはシーケンスで、常にIDを保持します

  • 派生クラスがオーバーライドしてベースクラスメンバにチェーンする場合、ベースメンバはチェーンコードによって直接呼び出され、他のどこからも呼び出されないという公理。

(*)通常、多重継承をサポートする型をクラスではなく「インターフェイス」として宣言することを要求し、インターフェイスが通常のクラスでできることをすべて実行できないようにします。

一般化された多重継承を許可したい場合、他の何かが道を譲らなければなりません。XとYの両方がBから継承する場合、それらは両方とも同じメンバーMをオーバーライドし、基本実装にチェーンし、DがXとYから継承するがMをオーバーライドしない場合、タイプDのインスタンスqを指定すると、(( B)q).M()ですか?このようなキャストを許可しないと、すべてのオブジェクトを任意のベースタイプにアップキャストできるという公理に違反しますが、キャストおよびメンバー呼び出しの考えられる動作はメソッドチェーンに関する公理に違反します。クラスは、コンパイル対象の基本クラスの特定のバージョンと組み合わせてのみロードすることを要求できますが、それはしばしば厄介です。複数のルートで祖先に到達できるタイプをロードすることをランタイムに拒否させると、実行可能になる場合がありますが、しかし、多重継承の有用性を大きく制限します。競合が存在しない場合にのみ共有継承パスを許可すると、古いバージョンのXは古いまたは新しいYと互換性があり、古いYは古いまたは新しいXと互換性がありますが、新しいXおよび新しいYはどちらも、それ自体が重大な変更であると予想されることを何もしなかったとしても、互換性があります。

一部の言語とフレームワークでは、MIから得たものがそれを許可するためにgivenめなければならないものよりも重要であるという理論に基づいて、多重継承を許可しています。ただし、MIのコストは非常に高く、多くの場合、インターフェイスはMIの利点の90%をわずかなコストで提供します。


ダウン投票者はコメントを要求しますか?私の答えは、多重継承を禁止することで得られる意味上の利点に関して、他の言語にはない情報を提供すると信じています。複数の継承を許可するには他の有用なものを放棄する必要があるという事実は、MIに反対するために複数の継承よりもこれらの他のものを重視する人にとっては十分な理由です。
supercat 14

2
MIの問題は、単一継承で使用するのと同じ手法を使用して簡単に解決または完全に回避できます。あなたが言及した能力/公理は、2番目のものを除いてMIによって本質的に妨げられません...そして同じメソッドに対して異なる動作を提供する2つのクラスから継承している場合、とにかくその曖昧さを解決するのはあなたです。しかし、あなたがそれをしなければならないことに気づいたなら、あなたはおそらくすでに継承を悪用しているでしょう。
cHao

@cHao:派生クラスが親メソッドオーバーライドし、それらにチェーンしないようにする必要がある場合(インターフェイスと同じルール)、問題を回避できますが、それはかなり厳しい制限です。一つは、パターン使用している場合FirstDerivedFoo"のsのオーバーライドは、Bar保護された非仮想にチェーンが、何もしませんがFirstDerivedFoo_Bar、とSecondDerivedFooにオーバーライドチェーンの保護された非仮想SecondDerivedBar、および基本メソッドにアクセスすることを望んでいるコードは、それらの保護された非仮想メソッドを使用している場合、その実行可能なアプローチかもしれません
...-supercat

...(構文を無視するbase.VirtualMethod)しかし、そのようなアプローチを特に促進するための言語サポートについては知りません。「1行の」仮想メソッドを必要とせずに、非仮想保護メソッドと同じシグネチャを持つ仮想メソッドの両方に1ブロックのコードをアタッチするクリーンな方法があればいいのにと思います。ソースコード内)。
-supercat

MIには、クラスが基本メソッドを呼び出さないという固有の要件はなく、特別な理由も必要ありません。問題がSIにも影響することを考えると、MIを非難するのは少し奇妙に思えます。
cHao
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.