Javaは、言語をシンプルに保つという設計目標を回避するという理由で、多重継承を省略します。
Java(とそのエコシステム)は本当に「シンプル」なのだろうか。Pythonは複雑ではなく、複数の継承を持っています。主観的すぎることなく、私の質問は...
多重継承を多用するように設計されたコードの恩恵を受ける典型的な問題パターンは何ですか
Javaは、言語をシンプルに保つという設計目標を回避するという理由で、多重継承を省略します。
Java(とそのエコシステム)は本当に「シンプル」なのだろうか。Pythonは複雑ではなく、複数の継承を持っています。主観的すぎることなく、私の質問は...
多重継承を多用するように設計されたコードの恩恵を受ける典型的な問題パターンは何ですか
回答:
長所:
短所:
C ++では、直交機能の合成に使用される多重継承の良い例は、CRTPを使用して、たとえばゲームのコンポーネントシステムをセットアップする場合です。
私は例を書き始めましたが、実世界の例を見るほうが価値があると思います。Ogre3Dの一部のコードは、非常に直感的な方法で多重継承を使用しています。たとえば、MeshクラスはResourcesとAnimationContainerの両方から継承します。リソースはすべてのリソースに共通のインターフェイスを公開し、AnimationContainerはアニメーションセットを操作するためのインターフェイスを公開します。それらは関連していないので、メッシュは、さらにアニメーションのセットを追加できるリソースであると考えるのは簡単です。自然に感じますか?
あなたは見ることができ、このライブラリ内の他の例のように、道のメモリ割り当ては、クラスがCRTPクラスの変種を継承することにより、罰金粒方法で管理され、新たな過負荷状態にして削除します。
前述のように、多重継承の主な問題は、関連する概念の混合から生じます。これにより、言語は複雑な実装を設定する必要があり(C ++がダイアモンドの問題を処理する方法を参照してください...)、ユーザーはその実装で何が起こっているのかわからなくなります。たとえば、C ++での実装方法を説明するこの記事をお読みください。
言語からそれを削除すると、言語が物事を悪くするように強制されている方法を知らない人々を避けるのに役立ちます。しかし、場合によっては自然に感じない方法で考えることを余儀なくされます。たとえそれが端的なケースであっても、それはあなたが考えるかもしれないより頻繁に起こります。
ここではあまり掘り下げませんが、次のリンクhttp://docs.python.org/release/1.5.1p1/tut/multiple.htmlを介して、Pythonの多重継承を確実に理解できます。
セマンティクスの説明に必要な唯一のルールは、クラス属性の参照に使用される解決ルールです。これは、左から右への深さ優先です。したがって、DerivedClassNameで属性が見つからない場合、Base1で検索され、次に(再帰的に)Base1の基本クラスで検索され、見つからない場合にのみBase2で検索されます。
...
偶発的な名前の競合を避けるための慣習にPythonが依存していることを考えると、多重継承を無差別に使用することは保守の悪夢であることは明らかです。多重継承に関するよく知られた問題は、共通の基本クラスを持つ2つのクラスから派生したクラスです。この場合に何が起こるかを把握するのは十分簡単ですが(インスタンスには、共通の基本クラスで使用される「インスタンス変数」またはデータ属性の単一のコピーがあります)、これらのセマンティクスが何らかの形であるかは明らかではありません有用。
これはほんの小さなパラグラフですが、私が推測する疑問をクリアするのに十分な大きさです。
多重継承が役立つ1つの場所は、クラスがいくつかのインターフェースを実装する状況ですが、各インターフェースにデフォルトの機能をいくつか組み込みたい場合です。これは、何らかのインターフェースを実装するほとんどのクラスが同じ方法を実行したい場合に便利ですが、場合によっては別の処理を行う必要があります。各クラスに同じ実装を使用できますが、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
。