多重継承を使用するのは良いコンセプトですか、代わりに他のことを行うことができますか?
多重継承(MIと略記) においがする、つまり 通常、それは悪い理由で行われ、メンテナーの前で吹き飛ばされます。
これは継承についても当てはまるため、多重継承についてはさらに当てはまります。
オブジェクトは本当に別のものから継承する必要がありますか? Car
は、Engine
から継承する必要も、Wheel
から継承する必要もありません。 Car
には、Engine
と4つのWheel
があります。
構成ではなく多重継承を使用してこれらの問題を解決する場合、何か間違ったことをしていることになります。
通常、クラスA
があり、B
とC
は両方ともA
を継承します。そして(理由を聞かないで)D
がB
とC
の両方を継承する必要があると判断した人。
私はこの種の問題に8 8年で2回遭遇しました。
D
はB
とC
の両方から継承すべきではありません)。これは悪いアーキテクチャであったためです(実際、C
はまったく存在しないはずです...)A
がその孫クラスD
に2回存在していたため、1つの親フィールドA::field
を更新すると、2回(B::field
を介して)更新されるため、 C::field
)、または何かが静かに間違ってクラッシュした場合、後で(B::field
にポインターを追加し、C::field
...を削除します)C++でキーワードvirtualを使用して継承を修飾すると、これが必要なものではない場合、上記の二重レイアウトが回避されますが、とにかく、私の経験では、おそらく何か間違ったことをしています...
オブジェクト階層では、階層をグラフとしてではなく、ツリー(ノードの親が1つ)として保持するようにしてください。
C++のDiamond of Dreadの本当の問題(デザインが健全であると仮定して-コードをレビューしてください!)、それは あなたは選択をする必要があります:
A
がレイアウトに2回存在することは望ましいことですか?それはどういう意味ですか? 「はい」の場合、必ずそれから2回継承します。この選択は問題に固有のものであり、C++では、他の言語とは異なり、言語レベルで設計を強制することなく実際に行うことができます。
しかし、すべての力と同様に、その力には責任が伴います。設計を見直してください。
ゼロまたは1つの具象クラスとゼロまたは1つ以上のインターフェイスの多重継承は、通常は大丈夫です。これは、上記のダイヤモンドの恐怖に遭遇しないからです。実際、これがJavaでの処理方法です。
通常、CがA
およびB
から継承する場合、ユーザーはC
をA
であるかのように、および/またはB
であるかのように使用できます。
C++では、インターフェイスは次のものを持つ抽象クラスです。
ゼロから1つの実オブジェクトへの多重継承、およびゼロ以上のインターフェースは「臭い」とは見なされません(少なくとも、それほどではありません)。
まず、NVIパターンを使用してインターフェイスを作成できます。 本当の基準は状態がないことです (つまり、this
を除くメンバー変数はありません)。抽象インターフェイスのポイントは、契約を公開することです(「このように、このように呼び出すことができます」)。抽象仮想メソッドのみを持つことの制限は、設計上の選択であり、義務ではありません。
第二に、C++では、(追加のコスト/インダイレクションがあったとしても)抽象インターフェースから仮想的に継承することは理にかなっています。そうしないと、インターフェイスの継承が階層内で複数回表示される場合、あいまいさがあります。
第三に、オブジェクトの向きは素晴らしいですが、そうではありません 唯一の真実TM C++で。適切なツールを使用し、C++にはさまざまな種類のソリューションを提供する他のパラダイムがあることを常に覚えておいてください。
時々、はい。
通常、C
クラスはA
とB
から継承し、A
とB
は2つの無関係なオブジェクトです(つまり、同じ階層にない、共通するものがない、異なる概念など)。
たとえば、X、Y、Z座標を持つNodes
のシステムがあり、多くの幾何学的計算(おそらく点、幾何学的オブジェクトの一部)を実行でき、各Nodeは自動エージェントであり、他のエージェントと通信できます。
おそらく、それぞれ独自の名前空間(名前空間を使用するもう1つの理由...)を持つ2つのライブラリに既にアクセスできます。1つはgeo
で、もう1つはai
です
したがって、独自のown::Node
がai::Agent
とgeo::Point
の両方から派生しています。
これは、代わりに作曲を使うべきではないかと自問するべき瞬間です。 own::Node
が実際にai::Agent
とgeo::Point
の両方である場合、合成は行われません。
次に、own::Node
が3D空間での位置に応じて他のエージェントと通信するように、複数の継承が必要になります。
(ai::Agent
とgeo::Point
は完全に、完全に、完全に非関連であることに注意してください。これにより、多重継承の危険性が大幅に減少します)
他の場合があります:
this
を介して他の部分と通信する必要がある場合)場合によってはコンポジションを使用でき、時にはMIの方が優れています。ポイントは次のとおりです。選択肢があります。責任を持って(そしてコードをレビューして)ください。
ほとんどの場合、私の経験では、いいえ。 MIは、たとえ機能しているように見えるとしても、結果を認識せずにフィーチャを積み重ねるのに怠慢な人が使用できるため、適切なツールではありません(Car
をEngine
とWheel
の両方にするなど)。
しかし、時々、はい。そして、その時点では、MIほどうまくいくものはありません。
しかし、MIは臭いので、コードレビューでアーキテクチャを守る準備をします(そして、それを守ることは良いことです。なぜなら、それを守ることができないなら、それをするべきではないからです)。
多重継承でできることは単一継承でもできるので、人々は多重継承は必要ないと言っています。あなたは、私が述べた委任トリックを使用するだけです。さらに、単一の継承で行うことは、クラスを介して転送することで継承なしでも実行できるため、継承はまったく必要ありません。実際には、ポインターとデータ構造を使用してすべて実行できるため、クラスも必要ありません。しかし、なぜそれをしたいのでしょうか?言語機能の使用はいつ便利ですか?回避策はいつ必要ですか?多重継承が有用なケースを見てきましたし、非常に複雑な多重継承が有用なケースも見ました。一般的に、回避策を実行するために、言語によって提供される機能を使用することを好みます
それを避ける理由はなく、状況によっては非常に有用です。ただし、潜在的な問題に注意する必要があります。
最大のものは死のダイヤモンドです:
class GrandParent;
class Parent1 : public GrandParent;
class Parent2 : public GrandParent;
class Child : public Parent1, public Parent2;
これで、Child内にGrandParentの2つの「コピー」ができました。
C++はこれを考えており、仮想継承を行って問題を回避することができます。
class GrandParent;
class Parent1 : public virtual GrandParent;
class Parent2 : public virtual GrandParent;
class Child : public Parent1, public Parent2;
常に設計を見直し、データの再利用を節約するために継承を使用していないことを確認してください。合成で同じことを表現できる場合(そして通常は可能)、これははるかに優れたアプローチです。
W: Multiple Inheritance を参照してください。
多重継承は批判を受けているため、多くの言語では実装されていません。批判には以下が含まれます:
- 複雑さの増加
- セマンティックなあいまいさは、多くの場合 diamond problem と要約されます。
- 単一のクラスから複数回明示的に継承できない
- クラスセマンティクスを変更する継承の順序。
C++/Javaスタイルコンストラクターを使用した言語での多重継承は、コンストラクターとコンストラクターチェーンの継承の問題を悪化させ、これらの言語でメンテナンスと拡張性の問題を引き起こします。構築方法が大きく異なる継承関係にあるオブジェクトは、コンストラクターチェーンパラダイムでは実装が困難です。
これを解決してCOMやJavaインターフェースなどのインターフェース(純粋な抽象クラス)を使用する最新の方法。
これの代わりに他のことをすることができますか?
はい、できます。 GoF から盗みます。
パブリック継承はIS-A関係であり、クラスは時には複数の異なるクラスのタイプになり、時にはこれを反映することが重要です。
「ミックスイン」も役立つ場合があります。通常、これらは小さなクラスであり、通常は何も継承せず、有用な機能を提供します。
継承階層がかなり浅く(ほぼ常にそうであるように)、適切に管理されている限り、恐ろしいダイヤモンドの継承を得ることはほとんどありません。ダイヤモンドは、多重継承を使用するすべての言語の問題ではありませんが、C++によるダイヤモンドの扱いは頻繁に厄介であり、時には不可解です。
多重継承が非常に便利な場合がありますが、実際には非常にまれです。これは、複数の継承を実際に必要としない場合、他の設計方法を使用することを好むためです。混乱する言語構造を避けることを好みます。何が起こっているのかを理解するためにマニュアルを本当によく読まなければならない継承の場合は簡単に構築できます。
多重継承を「回避」するべきではありませんが、「ダイヤモンドの問題」( http://en.wikipedia.org/wiki/Diamond_problem )などの発生する可能性のある問題を認識し、処理する必要があります。すべての力ですべきであるように、注意して与えられた力。
エッフェルを使用します。優れたMIがあります。心配ない。問題ない。簡単に管理できます。 MIを使用しない場合があります。ただし、A)それをうまく管理できない危険な言語である-OR- B)長年にわたってMIを回避してきた方法に満足している-OR- C)その他の理由(数が多すぎてリストに入れることはできません。上記の回答を参照してください。
私たちにとって、Eiffelを使用すると、MIは他のツールと同じくらい自然で、ツールボックス内の別のすばらしいツールです。率直に言って、私たちは他の誰もエッフェルを使用していないことにまったく関心を持っています。心配ない。私たちは私たちが持っているものに満足しているので、ぜひご覧ください。
探している間:Void-safetyとNullポインターの逆参照の撲滅に特に注意してください。私たちは皆MIを中心に踊っていますが、あなたの指針は失われつつあります! :-)
慎重に使用する必要があります。 Diamond Problem など、状況が複雑になる場合があります。
(ソース: learncpp.com )
すべてのプログラミング言語は、賛否両論のあるオブジェクト指向プログラミングの扱いが少し異なります。 C++のバージョンは、パフォーマンスを重視しており、無効なコードを書くのは非常に簡単であるという欠点があります。これは多重継承にも当てはまります。結果として、プログラマーをこの機能から遠ざける傾向があります。
他の人々は、多重継承が何の役に立たないかという問題に取り組んでいます。しかし、多かれ少なかれそれを避ける理由はそれが安全ではないからだということを暗示しているというコメントをかなり見ました。はい、そうです。
C++でよくあることですが、基本的なガイドラインに従えば、「肩越しに」常に監視しなくても安全に使用できます。重要な考え方は、「ミックスイン」と呼ばれる特別な種類のクラス定義を区別することです。クラスは、そのすべてのメンバー関数が仮想(または純粋仮想)の場合、ミックスインです。次に、単一のメインクラスと、必要な数の「ミックスイン」から継承できますが、キーワード「virtual」を使用してミックスインを継承する必要があります。例えば.
class CounterMixin {
int count;
public:
CounterMixin() : count( 0 ) {}
virtual ~CounterMixin() {}
virtual void increment() { count += 1; }
virtual int getCount() { return count; }
};
class Foo : public Bar, virtual public CounterMixin { ..... };
クラスをミックスインクラスとして使用する場合は、命名規則を採用して、コードを読んでいる人が何が起こっているのかを確認し、基本的なガイドラインのルールに従ってプレイしていることを確認できるようにすることをお勧めします。また、仮想ベースクラスが機能するという理由だけで、ミックスインに既定のコンストラクターが含まれていると、はるかにうまく機能することがわかります。そして、すべてのデストラクタも仮想化することを忘れないでください。
ここでの「ミックスイン」という単語の使用は、パラメーター化されたテンプレートクラスとは異なります(良い説明については this link を参照してください)が、用語の公正な使用であると思います。
これが、多重継承を安全に使用する唯一の方法であるという印象を与えたくありません。これは、確認が非常に簡単な1つの方法にすぎません。
少し抽象的になるリスクがあるが、カテゴリー理論の枠内で継承について考えることは光を当てる。
継承関係を示すすべてのクラスとそれらの間の矢印を考えると、このようなもの
A --> B
class B
はclass A
から派生することを意味します。与えられたことに注意してください
A --> B, B --> C
cはAから派生するBから派生するため、CもAから派生すると言われているため、
A --> C
さらに、すべてのクラスA
がA
から派生するのはA
であるため、継承モデルはカテゴリの定義を満たします。より伝統的な言語では、カテゴリClass
があり、すべてのクラスと継承関係をモーフィズムとするオブジェクトがあります。
それは少しセットアップですが、それでは私たちのDoom of Doomを見てみましょう:
C --> D
^ ^
| |
A --> B
これは日陰に見える図ですが、実際にはそうです。したがって、D
は、A
、B
、およびC
のすべてから継承します。さらに、OPの質問への対処に近づくと、D
はA
のスーパークラスからも継承します。図を描くことができます
C --> D --> R
^ ^
| |
A --> B
^
|
Q
さて、ここで死のダイヤモンドに関連する問題は、C
とB
がいくつかのプロパティ/メソッド名を共有し、物事が曖昧になるときです。ただし、共有動作をA
に移動すると、あいまいさがなくなります。
カテゴリ用語で言えば、A
、B
、およびC
は、B
およびC
がQ
から継承する場合、A
は、Q
のサブクラスとして書き換えることができます。これにより、A
が プッシュアウト と呼ばれます。
D
には pullback と呼ばれる対称構造もあります。これは、本質的に、B
とC
の両方から継承する、構築できる最も一般的な便利なクラスです。つまり、R
とB
を継承する他のクラスC
が複数ある場合、D
はR
がサブクラスとして書き換えられるクラスですof D
。
ダイヤモンドのヒントがプルバックおよびプッシュアウトであることを確認することで、名前の衝突やメンテナンスの問題を一般的に処理する素晴らしい方法が得られます。
注Paercebal の answer は、上記のモデルが示唆するように、彼の忠告が暗示されているため、これに影響を与えましたすべての可能なクラスの完全なカテゴリクラスで作業します。
私は彼の議論を、多重継承関係がどれほど複雑で強力かつ問題にならないかを示すものに一般化したかった。
TL; DRプログラム内の継承関係は、カテゴリを形成していると考えてください。その後、多重継承クラスをプッシュアウトして対称的に作成し、プルバックである共通の親クラスを作成することにより、Diamond of Doomの問題を回避できます。
この記事は、継承を説明するのに非常に役立ち、危険です。
具体的なオブジェクトのMIの重要な問題は、「AでありBである」合法的にすべきオブジェクトがめったにないことであるため、論理的な理由でめったに正しいソリューションとはなりません。多くの場合、「CがAまたはBとして機能できる」に従うオブジェクトCがあります。これは、インターフェイスの継承と構成によって実現できます。ただし、間違えないでください。複数のインターフェイスの継承は依然としてMIであり、そのサブセットにすぎません。
特にC++の場合、この機能の主な弱点は、多重継承の実際の存在ではありませんが、許可されるいくつかの構成体は、ほとんど常に不正です。たとえば、次のような同じオブジェクトの複数のコピーを継承します。
class B : public A, public A {};
dEFINITIONによって不正な形式です。英語に翻訳すると、これは「BはAとAです」です。そのため、人間の言語であっても、深刻なあいまいさがあります。 「Bには2つのAsがあります」または「BはAです」という意味ですか?そのような病理学的なコードを許可し、さらに悪い例として使用例にすると、機能を後継言語で保持することを主張することになると、C++は好意的になりませんでした。
ダイヤモンドパターンを超えて、多重継承はオブジェクトモデルを理解しにくくする傾向があり、その結果、メンテナンスコストが増加します。
作文は本質的に理解、理解、説明が簡単です。コードを書くのは面倒ですが、良いIDE(Visual Studioで作業してから数年が経ちましたが、確かにJava IDEにはすべて素晴らしい合成ショートカットがあります自動化ツール)は、そのハードルを克服する必要があります。
また、メンテナンスの観点から、「ダイヤモンドの問題」は非リテラル継承インスタンスでも発生します。たとえば、AとBがあり、クラスCで両方を拡張し、Aにオレンジジュースを作成する 'makeJuice'メソッドがあり、それを拡張してライムのひねりを加えたオレンジジュースを作成する場合: B 'は電流を生成する' makeJuice 'メソッドを追加しますか? 「A」と「B」は互換性のある「親」である可能性があります現在ですが、だからと言って常にそうだというわけではありません!
全体的に、継承、特に多重継承を避ける傾向があるという格言は健全です。すべての格言として、例外がありますが、コーディングする例外を指す点滅する緑色のネオンサインがあることを確認する必要があります(そして、そのような継承ツリーを見るたびに独自の点滅する緑色のネオンで描くように脳を訓練します)署名)、そして時々それがすべて意味をなすことを確認するためにチェックすること。
関係するクラスごとに4/8バイトかかります。 (クラスごとに1つのこのポインター)。
これは決して心配になることはないかもしれませんが、いつの日か数十億回インスタンス化されるマイクロデータ構造がある場合はそうなります。
継承よりも構成を使用できます。
一般的な感じは、構図が優れているということであり、非常によく議論されています。