なぜ多重継承を避けるべきなのか?質問する

なぜ多重継承を避けるべきなのか?質問する

多重継承を使用するのは良い概念でしょうか、それとも代わりに他の方法があるのでしょうか?

ベストアンサー1

多重継承(略してMI)匂いつまり、いつものそれは悪い理由で行われたことであり、メンテナーの顔に跳ね返ることになるだろう。

まとめ

  1. 継承ではなく機能の構成を考慮する
  2. 恐怖のダイヤモンドに注意してください
  3. オブジェクトではなく複数のインターフェースの継承を検討する
  4. 多重継承が適切な場合もあります。適切な場合は、多重継承を使用してください。
  5. コードレビューで多重継承アーキテクチャを防御する準備をする

1. おそらく作曲でしょうか?

これは継承の場合に当てはまりますが、多重継承の場合はさらに当てはまります。

オブジェクトは本当に別のオブジェクトから継承する必要がありますか? A は動作するためCarに から継承する必要はなくEngine、 からも継承する必要はありませんWheel。 Aにはと 4 つのCarがあります。EngineWheel

これらの問題を解決するために合成ではなく多重継承を使用する場合は、何か間違ったことをしたことになります。

2. 恐怖のダイヤモンド

通常、クラス があるA場合、Bと はC両方とも を継承しますA。そして (理由を聞かないでください)、誰かが がとDの両方を継承しなければならないと決定します。BC

私は 8 年間で 2 回この種の問題に遭遇しましたが、次のような理由で見ていて面白いです。

  1. これは最初からどれほどの間違いだったことか (どちらの場合も、はと のD両方から継承すべきではなかった)、これは悪いアーキテクチャだった (実際、そもそも存在するべきではなかった...)BCC
  2. メンテナーはそのためにどれだけの費用を払っていたのでしょうか。C++ では、親クラスがAその孫クラスに 2 回存在していたDため、1 つの親フィールドを更新するということは、それを 2 回更新する (とを通じて) か、後で何かが暗黙のうちにエラーになってクラッシュする ( にポインターを新規作成し、削除する...)A::fieldかのいずれかを意味していました。B::fieldC::fieldB::fieldC::field

C++ でキーワード virtual を使用して継承を修飾すると、これが望ましくない場合、上記の二重レイアウトを回避できますが、いずれにしても、私の経験では、おそらく何か間違っていると思います...

オブジェクト階層では、階層をグラフではなくツリー (ノードには 1 つの親があります) として維持するようにしてください。

ダイヤモンドについての詳細 (2017-05-03 編集)

C++ の Diamond of Dread の本当の問題 (設計が適切であると仮定して、コードをレビューしてもらいましょう。)は、選択をする必要がある:

  • レイアウト内にクラスAが 2 つ存在することが望ましいですか。また、それは何を意味しますか。望ましい場合は、必ず 2 回継承してください。
  • 一度だけ存在する場合は、それを仮想的に継承します。

この選択は問題に固有のものであり、C++ では他の言語とは異なり、言語レベルで設計を強制する教義なしに実際にこれを行うことができます。

しかし、すべての力と同様に、その力には責任が伴います。デザインをレビューしてもらいましょう。

3. インターフェース

0 個または 1 個の具象クラスと 0 個以上のインターフェースの多重継承は通常は問題ありません。なぜなら、上で説明した Diamond of Dread に遭遇することはないからです。実際、Java ではこのように行われます。

A通常、 C が および を継承する場合、ユーザーはであるかのように、および/または であるかのようにBを使用できることを意味します。CAB

C++ では、インターフェースは次の要素を持つ抽象クラスです。

  1. すべてのメソッドが純粋仮想として宣言されている(接尾辞 = 0) (2017-05-03 で削除)
  2. メンバー変数なし

0 個から 1 個の実際のオブジェクトと 0 個以上のインターフェースの多重継承は、「臭い」とは見なされません (少なくとも、それほどではありません)。

C++ 抽象インターフェースの詳細 (2017-05-03 編集)

まず、NVIパターンはインターフェースを作成するために使用できます。本当の基準は国家を持たないことだ(つまり、 を除くメンバー変数はありませんthis)。抽象インターフェースの目的は、契約を公開すること (「このように、このように呼び出すことができます」) であり、それ以上でもそれ以下でもありません。抽象仮想メソッドのみを持つという制限は、設計上の選択であり、義務ではありません。

2 番目に、C++ では、抽象インターフェースから仮想的に継承することが理にかなっています (追加のコストや間接性があっても)。そうしないと、インターフェースの継承が階層内に複数回出現し、あいまいさが生じます。

3つ目に、オブジェクト指向は素晴らしいが、そこに存在する唯一の真実TMC++ では適切なツールを使用し、C++ にはさまざまな種類のソリューションを提供する他のパラダイムがあることを常に覚えておいてください。

4. 多重継承は本当に必要ですか?

時々、そうです。

通常、クラスはとCを継承しており、と は2 つの無関係なオブジェクトです (つまり、同じ階層になく、共通点がなく、概念が異なるなど)。ABAB

たとえば、NodesX、Y、Z 座標を持つシステムがあり、多くの幾何学的計算 (おそらく点、幾何学的オブジェクトの一部) を実行でき、各ノードは自動エージェントであり、他のエージェントと通信できます。

おそらく、すでに2つのライブラリにアクセスしていて、それぞれに独自の名前空間(名前空間を使用するもう1つの理由...でも、名前空間は使用していますよね?)があり、1つgeoai

つまり、と のown::Node両方から独自の を導出できることになります。ai::Agentgeo::Point

ここで、代わりに合成を使用するべきかどうかを自問する必要があります。 がown::Node実際に aai::Agentと a の両方である場合geo::Point、合成は機能しません。

own::Node次に、 3D 空間内の位置に応じて他のエージェントと通信できるように、多重継承が必要になります。

ai::Agent(と はgeo::Point完全に無関係であることに気づくでしょう...これにより多重継承の危険性が大幅に軽減されます)

Other cases (edit 2017-05-03)

There are other cases:

  • using (hopefully private) inheritance as implementation detail
  • some C++ idioms like policies could use multiple inheritance (when each part needs to communicate with the others through this)
  • the virtual inheritance from std::exception (Is Virtual Inheritance necessary for Exceptions?)
  • etc.

Sometimes you can use composition, and sometimes MI is better. The point is: You have a choice. Do it responsibly (and have your code reviewed).

5. So, should I do Multiple Inheritance?

Most of the time, in my experience, no. MI is not the right tool, even if it seems to work, because it can be used by the lazy to pile features together without realizing the consequences (like making a Car both an Engine and a Wheel).

But sometimes, yes. And at that time, nothing will work better than MI.

But because MI is smelly, be prepared to defend your architecture in code reviews (and defending it is a good thing, because if you're not able to defend it, then you should not do it).

おすすめ記事