153

オブジェクト指向システムでコードを最適に拡張、拡張、および再利用する方法については、次の2つの考え方があります。

  1. 継承:サブクラスを作成して、クラスの機能を拡張します。サブクラスのスーパークラスメンバーをオーバーライドして、新しい機能を提供します。スーパークラスが特定のインターフェースを必要としているが、その実装にとらわれない場合に、メソッドを抽象/仮想にして、サブクラスを「空白を埋める」ように強制します。

  2. 集約:他のクラスを取得し、それらを新しいクラスに結合することにより、新しい機能を作成します。他のコードとの相互運用性のために、この新しいクラスに共通のインターフェースを接続します。

それぞれのメリット、コスト、および結果は何ですか?他に選択肢はありますか?

この議論は定期的に行われていると思いますが、Stack Overflowではまだ質問されていないと思います(関連する議論はいくつかありますが)。それのための良いグーグルの結果の驚くべき欠如もあります。

4

12 に答える 12

194

どちらが優れているかではなく、いつ何を使うかが重要です。

「通常の」ケースでは、単純な質問で、継承または集約が必要かどうかを判断できます。

  • 新しいクラス、多かれ少なかれ元のクラスと同じです。継承を使用します。新しいクラスは、元のクラスのサブクラスになりました。
  • 新しいクラスに元のクラスが必要な場合。集計を使用します。新しいクラスには、元のクラスがメンバーとして含まれるようになりました。

ただし、大きなグレーゾーンがあります。そのため、他にもいくつかのトリックが必要です。

  • 継承を使用した (または使用する予定である) が、インターフェイスの一部しか使用していない場合、または相互関係を論理的に保つために多くの機能をオーバーライドする必要がある場合。次に、集約を使用しなければならなかったことを示す大きな不快な臭いがします。
  • 集計を使用したことがある (または使用する予定である) が、ほぼすべての機能をコピーする必要があることがわかった場合。次に、継承の方向を示す匂いがあります。

短くするために。インターフェイスの一部が使用されていない場合、または非論理的な状況を避けるために変更する必要がある場合は、集約を使用する必要があります。大きな変更を加えずにほぼすべての機能が必要な場合にのみ、継承を使用する必要があります。疑わしい場合は、集約を使用してください。

元のクラスの機能の一部を必要とするクラスがある場合の別の可能性は、元のクラスをルートクラスとサブクラスに分割することです。そして、新しいクラスがルート クラスから継承されるようにします。ただし、非論理的な分離を作成しないように、これに注意する必要があります。

例を追加しましょう。「食べる」、「歩く」、「吠える」、「遊ぶ」というメソッドを持つクラス「犬」があります。

class Dog
  Eat;
  Walk;
  Bark;
  Play;
end;

ここで、「食べる」、「歩く」、「喉を鳴らす」、「遊ぶ」を必要とするクラス「Cat」が必要です。したがって、最初に Dog から拡張してみてください。

class Cat is Dog
  Purr; 
end;

わかりましたが、待ってください。この猫は吠えることができます (猫好きはそのために私を殺します)。そして、吠える猫は宇宙の理に反しています。そのため、何もしないように Bark メソッドをオーバーライドする必要があります。

class Cat is Dog
  Purr; 
  Bark = null;
end;

わかりました、これは機能しますが、悪臭がします。それでは、集計を試してみましょう。

class Cat
  has Dog;
  Eat = Dog.Eat;
  Walk = Dog.Walk;
  Play = Dog.Play;
  Purr;
end;

わかりました、これはいいです。この猫はもう吠えません。しかし、それでも、外に出たいという内なる犬がいます。それでは、解決策 3 を試してみましょう。

class Pet
  Eat;
  Walk;
  Play;
end;

class Dog is Pet
  Bark;
end;

class Cat is Pet
  Purr;
end;

これははるかにきれいです。室内犬なし。そして猫と犬は同じレベルです。モデルを拡張するために他のペットを導入することもできます。魚か、歩かないものでない限り。その場合、再度リファクタリングする必要があります。しかし、それはまた別の機会に。

于 2008-11-06T17:28:16.003 に答える
40

GOFの冒頭で彼らは述べています

クラスの継承よりもオブジェクトの構成を優先します。

これについては、ここでさらに説明します

于 2008-11-06T17:28:00.573 に答える
30

違いは通常、「is a」と「has a」の違いとして表現されます。継承、つまり「ある」関係は、リスコフの置換原理にうまくまとめられています。「has a」関係である集約は、まさにそれです。つまり、集約オブジェクト集約オブジェクトの 1 つを持っていることを示します。

さらに別の区別も存在します。C++ のプライベート継承は、(公開されていない) メンバー オブジェクトの集約によってもモデル化できる "の観点から実装されている" 関係を示します。

于 2008-11-06T17:25:52.840 に答える
15

これが私の最も一般的な議論です:

どのオブジェクト指向システムでも、どのクラスにも 2 つの部分があります。

  1. そのインターフェース: オブジェクトの「公開面」。これは、世界中に発表する一連の機能です。多くの言語では、セットは「クラス」として明確に定義されています。通常、これらはオブジェクトのメソッド シグネチャですが、言語によって多少異なります。

  2. その実装: オブジェクトがそのインターフェイスを満たし、機能を提供するために行う「舞台裏」の作業。これは通常、オブジェクトのコードとメンバー データです。

OOP の基本原則の 1 つは、実装がクラス内にカプセル化される (つまり、隠される) ことです。部外者が見るべき唯一のものはインターフェースです。

サブクラスがサブクラスから継承する場合、通常は実装とインターフェイスの両方を継承します。これは、クラスの制約として両方を受け入れることを余儀なくされていることを意味します。

アグリゲーションでは、実装かインターフェースのいずれか、またはその両方を選択できますが、どちらかを強制されることはありません。オブジェクトの機能は、オブジェクト自体に任されています。好きなように他のオブジェクトに任せることができますが、最終的には自分自身に責任があります。私の経験では、これはより柔軟なシステム、つまり変更が容易なシステムにつながります。

そのため、オブジェクト指向ソフトウェアを開発するときは、ほとんどの場合、継承よりも集約を好みます。

于 2008-11-06T17:33:23.903 に答える
12

私は「ある」と「ある」のどちらが良いか答えました。.

基本的に私は他の人々に同意します: 継承は、同じデータが含まれているという理由だけでなく、派生クラスが本当に拡張しようとしている型である場合にのみ使用してください。継承とは、サブクラスがデータだけでなくメソッドも取得することを意味することに注意してください。

派生クラスがスーパークラスのすべてのメソッドを持つことは理にかなっていますか? それとも、派生クラスではこれらのメソッドを無視する必要があると静かに約束しますか? それとも、スーパークラスのメソッドをオーバーライドして、ノーオペレーションにして、誰もうっかり呼び出してしまわないようにしていますか? または、API ドキュメント生成ツールにヒントを与えて、ドキュメントからメソッドを省略しますか?

これらは、その場合、集約がより良い選択であるという強力な手がかりです。

于 2008-11-06T17:48:19.753 に答える
6

これと関連する質問に対して、「is-aとhas-a。概念的に異なる」という回答がたくさんあります。

私の経験で私が見つけた1つのことは、関係が「is-a」であるか「has-a」であるかを判断しようとすると失敗することです。現在、オブジェクトに対してその決定を正しく行うことができたとしても、要件を変更することは、将来のある時点でおそらく間違っていることを意味します。

私が見つけたもう1つのことは、継承階層の周りに多くのコードが記述されていると、継承から集約に変換するのが非常に難しいということです。スーパークラスからインターフェイスに切り替えるだけで、システム内のほぼすべてのサブクラスを変更できます。

また、この投稿の他の場所で述べたように、集約は継承よりも柔軟性が低い傾向があります。

したがって、どちらかを選択する必要があるときはいつでも、継承に反対する議論の完全な嵐があります。

  1. あなたの選択は、ある時点で間違ったものになる可能性があります
  2. 一度選択すると、その選択を変更することは困難です。
  3. 継承はより制約的であるため、より悪い選択になる傾向があります。

したがって、私は集約を選択する傾向があります-強いis-関係があるように見えても。

于 2008-11-06T18:12:07.083 に答える
3

質問は通常、構成対継承と表現され、以前にここで尋ねられました。

于 2008-11-06T17:31:50.863 に答える
3

これを元の質問へのコメントにしたかったのですが、300文字が噛み付いています[; <)。

注意が必要だと思います。まず、質問で作成された 2 つのかなり具体的な例よりも多くのフレーバーがあります。

また、対物レンズと器具を混同しないことが重要だと思います。選択した手法または方法論が主要な目的の達成をサポートすることを確認したい人もいますが、どの手法が最適かという文脈から外れた議論が非常に役立つとは思いません。明確なスイート スポットと共に、さまざまなアプローチの落とし穴を知ることは役に立ちます。

たとえば、何を達成しようとしているのか、何から始めればよいのか、制約は何か。

コンポーネント フレームワークを作成していますか? 特別な目的のものであっても? インターフェイスはプログラミング システムの実装から分離可能ですか、それとも別の種類のテクノロジを使用した実践によって達成されますか? インターフェイスの継承構造 (ある場合) を、それらを実装するクラスの継承構造から分離できますか? 実装が提供するインターフェースに依存するコードから、実装のクラス構造を隠すことは重要ですか? 同時に使用できる複数の実装がありますか?それとも、メンテナンスと拡張の結果として、時間の経過とともに変動が大きくなりますか? ツールや方法論に固執する前に、これ以上のことを考慮する必要があります。

最後に、抽象化の違いと、それをどのように考えるか (is-a と has-a など) を OO テクノロジのさまざまな機能にロックすることはそれほど重要ですか? 概念構造が一貫しており、あなたや他の人にとって管理しやすいものであれば、おそらくそうでしょう。しかし、それとあなたが最終的に作るかもしれないゆがみに奴隷にされないのが賢明です. たぶん、レベルを下げて、あまり厳格にしないのが最善です (ただし、他の人が何が起きているかがわかるように、適切なナレーションを残してください)。[私はプログラムの特定の部分を説明可能にするものを探しますが、より大きな勝利がある場合はエレガンスを求めることもあります. 常に最良のアイデアとは限りません。]

私はインターフェイスの純粋主義者であり、Java フレームワークの構築であろうと、COM 実装の編成であろうと、インターフェイスの純粋主義が適切な種類の問題やアプローチに惹かれます。私はそれを誓っていますが、それはすべてに適しているわけではなく、すべてに近いわけでもありません. (私はインターフェイスの純粋主義に対する深刻な反例を提供しているように見えるプロジェクトをいくつか持っているので、私がどのように対処するかを見るのは興味深いでしょう.)

于 2008-11-06T19:24:29.980 に答える
2

これらが適用される可能性のある場所について説明します。ゲーム シナリオでの両方の例を次に示します。さまざまな種類の兵士が登場するゲームがあるとします。各兵士は、さまざまなものを収納できるナップザックを持つことができます。

ここの継承? マリン、グリーンベレー、スナイパーがいます。これらは兵士のタイプです。そのため、派生クラスとして Marine、Green Beret、Sniper を持つ基本クラスの Soldier があります。

ここに集約? ナップザックには、手榴弾、銃 (さまざまな種類)、ナイフ、メディキットなどを入れることができます。兵士は、これらのいずれかを任意の時点で装備できます。ダメージが一定割合減る。兵士クラスには、防弾チョッキ クラスのオブジェクトと、これらのアイテムへの参照を含むナップザック クラスが含まれています。

于 2008-11-06T17:28:19.763 に答える
2

どちらか一方の議論ではないと思います。それだけです:

  1. is-a (継承) 関係は、has-a (構成) 関係よりも発生頻度が低くなります。
  2. 継承は、使用するのが適切な場合でも正しく行うのが難しいため、カプセル化を破ったり、実装を公開することで密結合を促進したりする可能性があるため、十分な注意を払う必要があります。

どちらにもそれぞれの役割がありますが、継承の方がリスクが高くなります。

もちろん、Shape 'having-a' Point クラスと Square クラスを持つことは意味がありません。ここで継承が必要です。

拡張可能なものを設計しようとするとき、人々は継承について最初に考える傾向がありますが、それは間違っています。

于 2008-11-06T17:31:42.033 に答える
1

支持は、両方の候補者が資格を得た場合に発生します。A と B は選択肢であり、あなたは A を支持します。その理由は、合成は一般化よりも拡張性/柔軟性の可能性を提供するからです。この拡張/柔軟性は、主に実行時/動的な柔軟性を指します。

メリットはすぐにはわかりません。メリットを確認するには、次の予期しない変更要求を待つ必要があります。したがって、ほとんどの場合、一般化に固執する人は、構成を受け入れる人に比べて失敗します (後述する 1 つの明白なケースを除く)。したがって、ルール。学習の観点から、依存性注入を正常に実装できる場合は、どちらをいつ優先するかを知る必要があります。ルールは、意思決定にも役立ちます。よくわからない場合は、構成を選択してください。

概要: 構成 :小さなものを大きなものに差し込むだけで結合が減り、大きなオブジェクトが小さなオブジェクトを呼び戻すだけです。一般化: API の観点からすると、メソッドをオーバーライドできるように定義することは、メソッドを呼び出すことができるように定義するよりも強力なコミットメントです。(一般化が勝つ機会はほとんどありません)。そして、コンポジションでは、大きなクラスではなくインターフェースから継承も使用していることを決して忘れないでください

于 2011-09-20T13:28:23.627 に答える
0

どちらのアプローチも、さまざまな問題を解決するために使用されます。1 つのクラスから継承する場合、必ずしも 2 つ以上のクラスを集約する必要はありません。

そのクラスがシールされているか、インターセプトする必要がある非仮想メンバーを持っているため、単一のクラスを集約する必要がある場合があります。そのため、継承に関して明らかに有効ではないプロキシレイヤーを作成しますが、プロキシしているクラスサブスクライブできるインターフェイスがあり、これはかなりうまく機能します。

于 2008-11-06T17:28:38.653 に答える