多重継承のユースケース


15

Javaは、言語をシンプルに保つという設計目標を回避するという理由で、多重継承を省略します。

Java(とそのエコシステム)は本当に「シンプル」なのだろうか。Pythonは複雑ではなく、複数の継承を持っています。主観的すぎることなく、私の質問は...

多重継承を多用するように設計されたコードの恩恵を受ける典型的な問題パターンは何ですか


8
Javaは、ごくわずかな理由で、非常に多くの完全に良いことを省略しています。MIの正当な正当化は期待できません。
DeadMG

2
Pythonの多重継承は間違いなくドラゴンの領域です。深さ優先の左から右への名前解決を使用するという事実は、保守性と理解の両方に重大な問題があります。浅いクラス階層では役立ちますが、深い階層では非常に直感に反します。
マークブース

Javaに多重継承が含まれていない理由は、Java開発者が自分の言語を簡単に習得したかったからだと思います。多重継承は、場合によっては非常に強力ですが、把握するのが難しく、さらに効果的に使用するのはさらに困難です。それはあなたがプログラミングの新入生に直面したいと思うものではありません。つまり、継承の概念に苦労している人に仮想継承をどのように説明しますか?また、多重継承は実装者側にとっても些細なことではないため、Java開発者はそれを省略することは双方にとって好都合です。
cmaster-モニカの復元16年

Javaは名目上型付けされています。Pythonはそうではありません。これにより、Pythonでの多重継承の実装と理解の両方がはるかに簡単になります。
ジュール

回答:


11

長所:

  1. 他の方法でモデル化するよりも、問題をより明確にモデル化できる場合があります。
  2. 異なるパーレントに直交する目的がある場合、何らかの合成を許可できます

短所:

  1. 異なる親に直交する目的がない場合、タイプを理解するのが難しくなります。
  2. 言語(どの言語)で実装されているかを理解するのは簡単ではありません。

C ++では、直交機能の合成に使用される多重継承の良い例は、CRTPを使用して、たとえばゲームのコンポーネントシステムをセットアップする場合です。

私は例を書き始めましたが、実世界の例を見るほうが価値があると思います。Ogre3Dの一部のコードは、非常に直感的な方法で多重継承を使用しています。たとえば、MeshクラスはResourcesとAnimationContainerの両方から継承します。リソースはすべてのリソースに共通のインターフェイスを公開し、AnimationContainerはアニメーションセットを操作するためのインターフェイスを公開します。それらは関連していないので、メッシュは、さらにアニメーションのセットを追加できるリソースであると考えるのは簡単です。自然に感じますか?

あなたは見ることができ、このライブラリ内の他の例のように、道のメモリ割り当ては、クラスがCRTPクラスの変種を継承することにより、罰金粒方法で管理され、新たな過負荷状態にして削除します。

前述のように、多重継承の主な問題は、関連する概念の混合から生じます。これにより、言語は複雑な実装を設定する必要があり(C ++がダイアモンドの問題を処理する方法を参照してください...)、ユーザーはその実装で何が起こっているのかわからなくなります。たとえば、C ++での実装方法を説明するこの記事をお読みください

言語からそれを削除すると、言語が物事を悪くするように強制されている方法を知らない人々を避けるのに役立ちます。しかし、場合によっては自然に感じない方法で考えることを余儀なくされます。たとえそれが端的なケースであっても、それはあなたが考えるかもしれないより頻繁に起こります。


「直交目的」などの用語をより明確にする問題の例で答えを
飾れ

では、何か追加してみましょう。
クライム

Ogre3Dは、デザインのインスピレーションを求める場所ではありません。シングルトンの感染を見たことがありますか?
-DeadMG

まず、相続人のシングルトンは実際にはシングルトンではなく、構築と破壊は明示的です。次に、Ogreはハードウェアシステム(または必要に応じてグラフィックドライバー)上のレイヤーです。つまり、システムインターフェイス(ルートなど)の一意の表現は1つだけである必要があります。彼らはいくつかのシングルトンを削除できますが、それはここでのポイントではありません。私は自発的にこれを指摘することを避け、荒らしの議論を避けるようにしたので、私が指摘した例を見てください。シングルトンの使用は完全ではないかもしれませんが、実際には明らかに有用です(ただし、すべてのシステムではなく、その種類のシステムに対してのみ)。
クライム

4

より動的な言語で頻繁に使用されるmixinと呼ばれる概念があります。多重継承は、ミックスインを言語でサポートできる1つの方法です。ミックスインは通常、クラスがさまざまな機能を蓄積する方法として使用されます。多重継承がなければ、集約/委任を使用して、クラスでmixin型の動作を取得する必要があります。これは、もう少し構文が重いです。


+1これは、実際には、多重継承を持つ正当な理由です。ミックスインには追加の意味が含まれます(「このクラスをスタンドアロンとして使用しないでください」)
-ashes999

2

私の選択は主にダイヤモンドの問題による問題に基づいていると思います。

さらに、委任または他の手段によって多重継承の使用を回避することがしばしば可能です。

最後の質問の意味がわかりません。しかし、それが「どの場合に多重継承が有用なのか」なら、基本的にオブジェクトBとCの機能を持つオブジェクトAを持ちたいすべての場合です。


2

ここではあまり掘り下げませんが、次のリンクhttp://docs.python.org/release/1.5.1p1/tut/multiple.htmlを介して、Pythonの多重継承を確実に理解できます

セマンティクスの説明に必要な唯一のルールは、クラス属性の参照に使用される解決ルールです。これは、左から右への深さ優先です。したがって、DerivedClassNameで属性が見つからない場合、Base1で検索され、次に(再帰的に)Base1の基本クラスで検索され、見つからない場合にのみBase2で検索されます。

...

偶発的な名前の競合を避けるための慣習にPythonが依存していることを考えると、多重継承を無差別に使用することは保守の悪夢であることは明らかです。多重継承に関するよく知られた問題は、共通の基本クラスを持つ2つのクラスから派生したクラスです。この場合に何が起こるかを把握するのは十分簡単ですが(インスタンスには、共通の基本クラスで使用される「インスタンス変数」またはデータ属性の単一のコピーがあります)、これらのセマンティクスが何らかの形であるかは明らかではありません有用。

これはほんの小さなパラグラフですが、私が推測する疑問をクリアするのに十分な大きさです。


1

多重継承が役立つ1つの場所は、クラスがいくつかのインターフェースを実装する状況ですが、各インターフェースにデフォルトの機能をいくつか組み込みたい場合です。これは、何らかのインターフェースを実装するほとんどのクラスが同じ方法を実行したい場合に便利ですが、場合によっては別の処理を行う必要があります。各クラスに同じ実装を使用できますが、1つの場所にプッシュする方が合理的です。


1
それには、一般化された多重継承が必要でしょうか、それともインターフェースが実装されていないメソッドのデフォルトの動作を指定できる手段が必要でしょうか?インターフェイスが、他のインターフェイスから継承するメソッドとは対照的に、インターフェイス自体が実装するメソッドのデフォルト実装のみを指定できる場合、そのような機能は、多重継承を困難にする二重ダイヤモンドの問題を完全に回避します。
supercat

1

多重継承を多用するように設計されたコードの恩恵を受ける典型的な問題パターンは何ですか?

これはほんの一例ですが、安全性を向上させ、呼び出し元またはサブクラス全体にカスケード変更を適用する誘惑を軽減するために非常に貴重だと思います。

最も抽象的でステートレスなインターフェースであっても、複数の継承が非常に役立つことがわかったのは、C ++の非仮想インターフェースイディオム(NVI)です。

契約の普遍性を実際に狭めるのではなく、契約の普遍的な側面を実施するためのほんの少しの実装を備えたインターフェースほど、実際には抽象基本クラスでさえありません。

単純な例(渡されたファイルハンドルが開いているか、そのようなものをチェックする人もいます):

// Non-virtual interface (public methods are nonvirtual/final).
// Since these are modeling the concept of "interface", not ABC,
// multiple will often be inherited ("implemented") by a subclass.
class SomeInterface
{
public:
    // Pre: x should always be greater than or equal to zero.
    void f(int x) /*final*/
    {
        // Make sure x is actually greater than or equal to zero
        // to meet the necessary pre-conditions of this function.
        assert(x >= 0);

        // Call the overridden function in the subtype.
        f_impl(x);
    }

protected:
    // Overridden by a boatload of subtypes which implement
    // this non-virtual interface.
    virtual void f_impl(int x) = 0;
};

この場合、多分f、コードベースの1000の場所から呼び出されますf_implが、100のサブクラスによってオーバーライドされます。

この種の安全性チェックは、電話をかけるすべての1000箇所fまたはオーバーライドする100箇所すべてで行うのは困難f_implです。

機能へのこのエントリポイントを非仮想にするだけで、このチェックを実行する中心的な場所が1つ得られます。そして、このチェックは、この関数を呼び出すために必要な前提条件を単に表明するだけなので、抽象化を少しでも減らすことはありません。ある意味では、インターフェイスによって提供されるコントラクトを間違いなく強化し、x入力をチェックして100か所すべての有効な前提条件に準拠していることを確認する負担を軽減します。

すべての言語が持っていて、C ++でさえ、もう少しネイティブな概念であることを願っています(例:オーバーライドするために別の関数を定義する必要はありません)。

これはassert、事前にこれを行わなかった場合に非常に役立ち、後でコードベースのランダムな場所に負の値が渡されたときに必要になることに気付きましたf


0

最初:基本クラスの複数のコピー(C ++の問題)および基本クラスと派生クラス間の密結合。

2番目:抽象インターフェースからの多重継承


どのような状況でも役に立たないことを提案していますか?そして、それなしですべてを便利に設計/コーディングできるということですか?また、2番目の点について詳しく説明してください。
ツリーコーダー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.