継承と集約[終了]


151

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

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

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

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

この議論は定期的に出てきますが、まだスタックオーバーフローで質問されているとは思いません(いくつか関連する議論があります)。また、Googleでの良い結果が驚くほど不足していることもあります。


9
いい質問ですが、残念ながら今は十分な時間がありません。
Toon Krijthe 08年

4
良い答えの方が速い答えより優れています...私は自分の質問を見て、少なくともあなたに投票します:-P
Craig Walker

回答:


181

どちらが最善であるかではなく、何をいつ使用するかが問題になります。

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

  • 新しいクラス元のクラスとほぼ同じ場合。継承を使用します。新しいクラスは、元のクラスのサブクラスになりました。
  • 新しいクラスは、必要がある場合は持って元のクラスを。集計を使用します。新しいクラスには、元のクラスがメンバーとして含まれています。

ただし、大きな灰色の領域があります。したがって、他のいくつかのトリックが必要です。

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

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

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

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

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

ここで、「食べる」、「歩く」、「パー」、および「再生」を必要とするクラス「猫」が必要です。だから最初に犬からそれを拡張してみてください。

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;

これはずっときれいです。内部の犬はありません。猫と犬は同じレベルです。他のペットを紹介してモデルを拡張することもできます。魚、または歩かないものでない限り。その場合、再びリファクタリングする必要があります。しかし、それはまた別のことです。


5
「クラスのほとんどすべての機能を再利用する」というのは、私が実際に継承を好むのはこの1回だけです。私が本当に望んでいるのは、「これらの特定のメソッドのためにこの集約オブジェクトに委任する」と簡単に言うことができる言語です。それは両方の長所です。
Craig Walker、

他の言語についてはわかりませんが、Delphiには、メンバーがインターフェイスの一部を実装できるメカニズムがあります。
Toon Krijthe 2008年

2
Objective Cはプロトコルを使用して、関数が必須かオプションかを決定できます。
cantfindaname88 2013年

1
実用的な例で素晴らしい答え。呼び出されるメソッドを使用してPetクラスを設計し、makeSound各サブクラスに独自の種類のサウンドを実装させます。これは、ペットがたくさんいる状況で役立ちます for_each pet in the list of pets, pet.makeSound
blongho


27

違いは通常、「is a」と「has a」の違いとして表されます。「is a」関係である継承は、Liskov Substitution Principleでうまくまとめられています。「ある」関係である集約は、まさにそれです-集約オブジェクト集約オブジェクトの1つを持っていることを示しています。

さらに別の違いもあります。C++のプライベート継承は「次の点で実装されている」関係を示します。これは、(非公開)メンバーオブジェクトの集約によってもモデル化できます。


また、あなたが回答することになっている質問そのものに違いがうまくまとめられています。翻訳を始める前に、まず質問を読んでもらいたい。エリートクラブのメンバーになるためにデザインパターンを盲目的に宣伝しますか?
Val

私はこのアプローチが好きではありません。それは作曲に関するものです
Alireza Rahmani Khalili

15

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

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

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

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

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

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

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

したがって、オブジェクト指向ソフトウェアを開発しているときはいつでも、継承よりも常に集約を優先します。


抽象クラスを使用してインターフェイスを定義し、各具象実装クラスに直接継承を使用すると思います(かなり浅くします)。集約は具体的な実装のもとに行われます。
orcmid 2008年

それ以外のインターフェースが必要な場合は、メインレベルの抽象化されたインターフェースのインターフェース上のメソッドを介してそれらを返し、リンスを繰り返します。
orcmid 2008年

このシナリオでは集約の方が良いと思いますか?本はSellingItemで、A DigitalDiscはSelliingItemのあるcodereview.stackexchange.com/questions/14077/...
LCJ

11

「ある」と「ある」の答えを出しました。どちらが良いですか?

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

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

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


6

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

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

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

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

したがって、どちらか一方を選択しなければならない場合はいつでも、継承に対する完全な論争が発生します。

  1. あなたの選択はおそらく間違ったものになるでしょう
  2. 一度選択すると、その選択を変更することは困難です。
  3. 継承は制約が多いため、より悪い選択になる傾向があります。

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


進化する要件は、オブジェクト指向の設計が必然的に間違っていることを意味します。継承と集約は、その氷山の一角にすぎません。考えられるすべての未来を構築することはできないので、XPのアイデアに従ってください。今日の要件を解決し、リファクタリングが必要になる可能性があることを受け入れます。
Bill Karwin

私はこの質問と質問の行が好きです。ぼやけているis-a、has-a、uses-aが心配です。私はインターフェイスから始めます(インターフェイスに実装する抽象化を識別することに加えて)。間接参照の追加レベルは非常に貴重です。集計の結論には従わないでください。
orcmid 2008年

それはかなり私のポイントですが。継承と集約はどちらも問題を解決する方法です。これらの方法の1つでは、明日問題を解決しなければならないときに、あらゆる種類のペナルティが発生します。
Craig Walker

私の考えでは、集約+インターフェースは継承/サブクラス化の代替手段です。集計のみに基づいてポリモーフィズムを行うことはできません。
Craig Walker、

3

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


そうだね。SOが関連する質問の1つとしてそれを与えなかったのは面白い。
Craig Walker、

2
SO検索は依然として大きな時間を
費やす

同意した。だから私はこれを閉じなかった。それと、多くの人がすでにここで答えていました。
リザードに請求する

1
SOには、2つの質問(およびそれらの回答)を1にまとめる機能が必要です
Craig Walker、

3

私はこれを元の質問に対するコメントにしたかったのですが、300文字のバイト[; <)です。

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

また、目的と装置を混同しないことが重要であることをお勧めします。選択した手法または方法論が主要な目的の達成をサポートすることを確認したいのですが、どの手法が最良のディスカッションが非常に有用であるかは、文脈から外れたものではありません。明確なスイートスポットとともに、さまざまなアプローチの落とし穴を知るのに役立ちます。

たとえば、達成するために何をしているのか、何から始めて利用できるのか、そして制約は何ですか?

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

最後に、抽象化の違いをロックし、オブジェクト指向テクノロジーのさまざまな機能にそれをどう捉えるか(is-aとhas-aのように)は重要ですか?もしそうなら、それが概念構造を一貫して保ち、あなたや他の人のために扱いやすいなら。しかし、それと、結局あなたが作り出すかもしれないゆがみにだまされないのが賢明です。たぶん、レベルを下げてそれほど厳格ではないのが最善です(ただし、他の人が何が起こっているのかわかるように良いナレーションを残してください)。[私はプログラムの特定の部分を説明可能にするものを探しますが、より大きな勝利があるとき、私は優雅さを求めます。常に最良のアイデアとは限りません。]

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


2

これらが適用される可能性のある部分について説明します。以下は、ゲームシナリオでの両方の例です。異なるタイプの兵士がいるゲームがあるとします。各兵士はさまざまなものを保持できるナップザックを持つことができます。

ここで継承? マリン、グリーンのベレー帽、狙撃兵がいます。これらは兵士のタイプです。だから、派生クラスとしてマリン、グリーンベレー、スナイパーの基本クラスの兵士がいます

ここに集約? ナップザックには手榴弾、銃(さまざまな種類)、ナイフ、メディキットなどを含めることができます。兵士はいつでもこれらのいずれかを装備できます。さらに、攻撃されたときに鎧として機能する防弾チョッキを持っていることもあります。けがは一定の割合に減少します。兵士クラスには、防弾チョッキクラスのオブジェクトと、これらのアイテムへの参照を含むナップザッククラスが含まれています。


2

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

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

どちらにも場所がありますが、継承はより危険です。

もちろん、クラスShape 'having-a' PointクラスとSquareクラスを用意しても意味がありません。相続は当然です。

拡張性のあるものを設計しようとするとき、人々は最初に継承について考える傾向があります。それが問題です。


1

両方の候補者が適格である場合に有利になります。AとBはオプションであり、Aを優先します。その理由は、コンポジションが一般化よりも拡張/柔軟性の可能性を提供するためです。この拡張/柔軟性は、主にランタイム/動的な柔軟性を指します。

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

概要:構成:小さなものを大きなものに接続するだけで結合が減少し、大きなオブジェクトは小さなオブジェクトを呼び出すだけです。生成:APIの観点から、メソッドをオーバーライドできることを定義することは、メソッドを呼び出すことができることを定義することよりも強いコミットメントです。(汎化が成功した場合はほとんどありません)。また、構成では、大きなクラスの代わりにインターフェースからの継承も使用していることを忘れないでください


0

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.