多重継承を使用する状況を考えられる人はいますか? 私が考えることができるすべてのケースは、メソッド演算子によって解決できます
AnotherClass() { return this->something.anotherClass; }
多重継承を使用する状況を考えられる人はいますか? 私が考えることができるすべてのケースは、メソッド演算子によって解決できます
AnotherClass() { return this->something.anotherClass; }
フルスケールの多重継承のほとんどの用途は、ミックスインです。例として:
class DraggableWindow : Window, Draggable { }
class SkinnableWindow : Window, Skinnable { }
class DraggableSkinnableWindow : Window, Draggable, Skinnable { }
等...
ほとんどの場合、厳密にインターフェイスの継承を行うには多重継承を使用するのが最善です。
class DraggableWindow : Window, IDraggable { }
次に、DraggableWindow クラスに IDraggable インターフェイスを実装します。優れた mixin クラスを作成するのは非常に困難です。
MI アプローチの利点は (Interface MI のみを使用している場合でも)、あらゆる種類の異なる Windows を Window オブジェクトとして扱うことができ、単一では不可能 (またはより困難) なものを柔軟に作成できることです。継承。
たとえば、多くのクラス フレームワークでは、次のように表示されます。
class Control { }
class Window : Control { }
class Textbox : Control { }
ここで、ウィンドウの特性を持つ Textbox が必要だとします。ドラッグ可能、タイトルバーなど...次のようなことができます:
class WindowedTextbox : Control, IWindow, ITexbox { }
単一継承モデルでは、Control オブジェクトの重複やその他の問題が発生しない限り、Window と Textbox の両方から簡単に継承することはできません。WindowedTextbox を Window、Textbox、または Control として扱うこともできます。
また、.anotherClass() イディオムに対応するために、.anotherClass() は別のオブジェクトを返しますが、多重継承により、同じオブジェクトを別の目的に使用できます。
多重継承は、mixinクラスを使用する場合に特に便利です。
ウィキペディアに記載されているように:
オブジェクト指向プログラミング言語では、ミックスインは、サブクラスによって継承される特定の機能を提供するクラスですが、スタンドアロンではありません。
当社の製品が mixin クラスを使用する方法の例は、構成の保存と復元の目的です。一連の純粋仮想メソッドを定義する抽象 mixin クラスがあります。保存可能なクラスはすべて、適切な保存/復元機能を自動的に提供する保存/復元 mixin クラスから継承します。
ただし、通常のクラス構造の一部として他のクラスから継承することもできるため、これらのクラスがこの点で多重継承を使用することは非常に一般的です。
多重継承の例:
class Animal
{
virtual void KeepCool() const = 0;
}
class Vertebrate
{
virtual void BendSpine() { };
}
class Dog : public Animal, public Vertebrate
{
void KeepCool() { Pant(); }
}
あらゆる形式の公開継承 (単一または複数) を行う場合に最も重要なことは、関係を尊重することです。クラスは、それらのオブジェクトの 1 つである場合にのみ、1 つまたは複数のクラスから継承する必要があります。それらのオブジェクトの 1 つを単に「含む」場合は、代わりに集約または構成を使用する必要があります。
犬は動物であり、脊椎動物でもあるため、上記の例は適切に構成されています。
ほとんどの人は、クラスに複数のインターフェイスを適用するコンテキストで多重継承を使用します。これは、Java や C# などで採用されているアプローチです。
C++ では、型間の is-a 関係で、複数の基本クラスをかなり自由に適用できます。そのため、派生オブジェクトをその基底クラスと同じように扱うことができます。
LeopardSkinPillBoxHat が指摘しているように、もう 1 つの用途はミックスインです。この優れた例は、Andrei Alexandrescu の本 Modern C++ Design のLoki ライブラリです。彼は、継承を通じて特定のクラスの動作または要件を指定するポリシー クラスと彼が呼ぶものを使用します。
さらに別の使用法は、しばしば恐ろしいダイアモンド階層で姉妹クラスの委譲を使用することにより、 API の独立性を可能にするモジュラー アプローチを簡素化するものです。
MI の用途は多数あります。虐待の可能性はさらに大きくなります。
Javaにはインターフェースがあります。C++ にはありません。
したがって、多重継承を使用してインターフェイス機能をエミュレートできます。C# と Java のプログラマーは、基本クラスを拡張するだけでなく、いくつかのインターフェイスを実装するクラスを使用するたびに、状況によっては多重継承が役立つ可能性があることを認めています。
定型コードに最も役立つと思います。たとえば、IDisposable パターンは、.NET のすべてのクラスでまったく同じです。では、なぜそのコードを何度も再入力するのでしょうか?
もう 1 つの例は ICollection です。インターフェイス メソッドの大部分は、まったく同じように実装されています。実際にクラスに固有のメソッドは 2 つだけです。
残念ながら、多重継承は非常に悪用されやすいです。LabelPrinter クラスが単に TcpIpConnector クラスを含むのではなく、TcpIpConnector クラスから継承するなど、人々はすぐにばかげたことを始めます。
私が最近取り組んだ 1 つのケースは、ネットワーク対応のラベル プリンターに関係していました。ラベルを印刷する必要があるため、LabelPrinter クラスがあります。このクラスには、複数の異なるラベルを印刷するための仮想呼び出しがあります。また、接続、送信、受信が可能な、TCP/IP 接続対象のジェネリック クラスもあります。そのため、プリンターを実装する必要があるときは、LabelPrinter クラスと TcpIpConnector クラスの両方から継承しました。
fmsf の例は悪い考えだと思います。車はタイヤでもエンジンでもない。そのためには、コンポジションを使用する必要があります。
MI (実装またはインターフェイスの) を使用して機能を追加できます。これらは多くの場合、ミックスイン クラスと呼ばれます。GUI があると想像してください。描画を扱うビュークラスとドラッグを扱うドラッグ&ドロップクラスがあります。両方を行うオブジェクトがある場合、次のようなクラスがあります
class DropTarget{
public void Drop(DropItem & itemBeingDropped);
...
}
class View{
public void Draw();
...
}
/* View you can drop items on */
class DropView:View,DropTarget{
}
C++ では、MI はフレームワーク (前述のミックスイン クラス) の一部として使用するのが最適であると思います。私が確かに知っている唯一のことは、アプリでそれを使用しようとするたびに、選択を後悔することになり、しばしばそれを引き裂いて生成されたコードに置き換えたことです.
MI は、「本当に必要な場合は使用するが、本当に必要な場合は必ず使用する」ツールの 1 つです。
インターフェース (Java や C# のようなもの) の構成とヘルパーへの転送により、多重継承 (特に mixin) の一般的な使用法の多くをエミュレートできることは事実です。ただし、これは、その転送コードが繰り返される (および DRY に違反する) という犠牲を払って行われます。
MI は確かに多くの困難な領域を切り開いており、最近では言語設計者の中には、MI の潜在的な落とし穴が利点よりも重要であるという決定を下した人もいます。
同様に、ジェネリック (異種コンテナーは機能し、ループは (末尾) 再帰に置き換えることができる) や、プログラミング言語のほとんどすべての機能に反対することができます。機能がなくても機能する可能性があるからといって、その機能が無価値であったり、ソリューションを効果的に表現するのに役立たないというわけではありません。
言語と言語ファミリーの豊富な多様性により、開発者は目の前のビジネス上の問題を解決する優れたツールを簡単に選択できます。道具箱にはめったに使わないものがたくさん入っていますが、そんなときはすべてを釘として扱いたくないものです。
当社の製品が mixin クラスを使用する方法の例は、構成の保存と復元の目的です。一連の純粋仮想メソッドを定義する抽象 mixin クラスがあります。保存可能なクラスはすべて、適切な保存/復元機能を自動的に提供する保存/復元 mixin クラスから継承します。
この例は、多重継承の有用性を実際には示していません。ここで定義されているのは INTERFACE です。多重継承により、動作も継承できます。これがミックスインのポイントです。
例; 下位互換性を維持する必要があるため、独自のシリアル化メソッドを実装する必要があります。
したがって、すべてのオブジェクトは、このように Read および Store メソッドを取得します。
Public Sub Store(ByVal File As IBinaryWriter)
Public Sub Read(ByVal File As IBinaryReader)
また、オブジェクトの割り当てと複製もできるようにしたいと考えています。したがって、すべてのオブジェクトでこれが必要です。
Public Sub Assign(ByVal tObject As <Class_Name>)
Public Function Clone() As <Class_Name>
現在VB6では、このコードを何度も繰り返しています。
Public Assign(ByVal tObject As ObjectClass)
Me.State = tObject.State
End Sub
Public Function Clone() As ObjectClass
Dim O As ObjectClass
Set O = New ObjectClass
O.State = Me.State
Set Clone = 0
End Function
Public Property Get State() As Variant
StateManager.Clear
Me.Store StateManager
State = StateManager.Data
End Property
Public Property Let State(ByVal RHS As Variant)
StateManager.Data = RHS
Me.Read StateManager
End Property
Statemanager は、バイト配列を読み取って格納するストリームであることに注意してください。
このコードは何十回も繰り返されます。
.NET では、ジェネリックと継承を組み合わせて使用することで、これを回避できます。.NET バージョンのオブジェクトは、MyAppBaseObject から継承するときに、Assign、Clone、および State を取得します。しかし、すべてのオブジェクトが MyAppBaseObject から継承されているという事実は好きではありません。
Assign Clone インターフェイスと BEHAVIOR をミックスするだけです。Read インターフェイスと Store インターフェイスを別々にミックスしてから、Assign と Clone をミックスできるようにすることをお勧めします。私の意見では、それはよりクリーンなコードになるでしょう。
しかし、私が動作を再利用する時間は、インターフェイスを使用するまでに矮小化されています。これは、ほとんどのオブジェクト階層の目標が動作の再利用ではなく、異なるオブジェクト間の関係を正確に定義することであるためです。どのインターフェイスが設計されているか。したがって、C# (または VB.NET) にこれを行う機能があればいいのですが、私の意見では、それはショー ストッパーではありません。
これが、インターフェースと継承の問題に関して最初にC ++がボールをいじったという問題でさえあるという全体的な理由です。OOP が登場したとき、ビヘイビアの再利用が最優先事項だと誰もが考えていました。しかし、これはキメラであり、UI フレームワークの作成など、特定の状況でのみ役立つことが判明しました。
その後、ミックスイン (およびアスペクト指向プログラミングにおけるその他の関連概念) のアイデアが開発されました。多重継承は、ミックスインの作成に役立つことがわかりました。しかし、C# は、これが広く認識される直前に開発されました。これを行うための代替構文が開発される可能性があります。
次の例は、主に C++ でよく目にするものです: 必要なユーティリティ クラスのために必要になる場合がありますが、それらの設計のために構成を介して使用することはできません (少なくとも効率的ではないか、元に戻るよりもコードを煩雑にすることなく)。複数の継承)。良い例は、抽象基本クラス A と派生クラス B があり、B も一種のシリアライズ可能なクラスである必要があるため、たとえばシリアライズ可能な別の抽象クラスから派生する必要がある場合です。MI を回避することは可能ですが、Serializable にいくつかの仮想メソッドしか含まれておらず、B のプライベート メンバーへの深いアクセスが必要な場合は、フレンド宣言を作成して B の内部へのアクセス権を一部に与えることを避けるために、継承ツリーを混乱させる価値があるかもしれません。ヘルパー構成クラス。
実は今日使わなきゃいけなかった…
これが私の状況でした.Aには0個以上のBが含まれ(配列で表されます)、各Bには0個以上のCがあり、CからDへのドメインモデルがメモリに表されていました。それらが配列であるという事実を変更することはできませんでした (これらの配列のソースは、ビルド プロセスから自動的に生成されたコードからのものでした)。各インスタンスは、親配列のどのインデックスに属しているかを追跡する必要がありました。また、親のインスタンスも追跡する必要がありました (理由が詳細すぎるため)。私はこのようなものを書きました (それにはもっと多くのことがあり、これは構文的に正しくありません。これは単なる例です):
class Parent
{
add(Child c)
{
children.add(c);
c.index = children.Count-1;
c.parent = this;
}
Collection<Child> children
}
class Child
{
Parent p;
int index;
}
次に、ドメインの種類について、次のようにしました。
class A : Parent
class B : Parent, Child
class C : Parent, Child
class D : Child
実際の実装はインターフェイスとジェネリックを使用した C# であり、言語でサポートされている場合のように多重継承を行うことはできませんでした (コピー ペーストを実行する必要がありました)。だから、私は SO を検索して、人々が多重継承についてどう考えているかを調べようと思い、あなたの質問を得ました ;)
親の追加の実装のため、.anotherClassのソリューションを使用できませんでした(これを参照します-これを他のクラスにしないことを望みました)。
生成されたコードには、親でも子でもない別のサブクラスがあったため、さらに悪化しました...さらにコピペ。