質問

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

  1. 継承:サブクラスを作成して、クラスの機能を拡張します。サブクラスのスーパークラスメンバーをオーバーライドして、新しい機能を提供します。サブクラスを強制的に<!> quot; fill-in-the-blanks <!> quot;にするには、メソッドを抽象/仮想にします。スーパークラスが特定のインターフェースを必要としているが、その実装については不可知な場合。

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

それぞれのメリット、コスト、および結果は何ですか?他の代替手段はありますか?

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

役に立ちましたか?

解決

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

「通常」の場合、継承または集計が必要かどうかを確認するには、簡単な質問で十分です。

  • 新しいクラスが 元のクラスと多少異なる場合。継承を使用します。新しいクラスは、元のクラスのサブクラスになりました。
  • 新しいクラスが元のクラスを持っている必要がある場合。集約を使用します。新しいクラスには、元のクラスがメンバーとして追加されました。

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

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

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

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

例を追加します。 「Eat」、「Walk」、「Bark」、「Play」というメソッドを持つクラス「Dog」があります。

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

「食べる」、「歩く」、「購入する」、「遊ぶ」を必要とするクラス「猫」が必要になりました。したがって、最初にDogから拡張してみてください。

class Cat is Dog
  Purr; 
end;

大丈夫ですが、待ってください。この猫はBarえることができます(猫愛好家は私を殺します)。そして、barえている猫は宇宙の原則に違反しています。そこで、何もしないようにBarkメソッドをオーバーライドする必要があります。

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

OK、これは機能しますが、悪臭がします。集約を試してみましょう:

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

わかりました、これはいいですね。この猫はもうbarえず、静かでもありません。しかし、まだそれは外に望んでいる内部犬を持っています。解決策3番を試してみましょう:

class Pet
  Eat;
  Walk;
  Play;
end;

class Dog is Pet
  Bark;
end;

class Cat is Pet
  Purr;
end;

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

他のヒント

GOF の冒頭に記載されています

  

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

これについては、こちら

でさらに詳しく説明しています。

違いは通常、<!> quot; is a <!> quot;および<!> quot;には<!> quot;があります。継承、<!> quot;は<!> quot;関係は、 Liskov Substitution Principle にまとめられています。集約、<!> quot;は<!> quot;関係は、それだけです-それは、集約オブジェクトが集約オブジェクトの1つを持っていることを示しています。

さらに区別があります-C ++のプライベート継承は、<!> quot;が<!> quotの観点から実装されていることを示します。 (非公開)メンバーオブジェクトの集約によってもモデル化できます。

ここに私の最も一般的な議論があります:

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

  1. そのインターフェース:<!> quot; public face <!> quot;オブジェクトの。これは、世界中に発表する一連の機能です。多くの言語では、セットは<!> quot; class <!> quot;に適切に定義されています。通常、これらはオブジェクトのメソッドシグネチャですが、言語によって少し異なります。

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

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

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

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

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

<!> quot; <!> quot; vs <!> quot; <!> quot; :どちらが良いですか?

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

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

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

多くの<!> quot; is-a vs. has-a;それらは概念的に異なります<!> quot;これと関連する質問に対する回答。

私が経験したことの1つは、関係が<!> quot; is-a <!> quot;であるかどうかを判断しようとすることです。または<!> quot; has-a <!> quot;失敗するはずです。今すぐオブジェクトの判断を正しく行うことができたとしても、要件の変更は、将来のある時点でおそらく間違っていることを意味します。

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

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

だから、どちらかを選択する必要があるときはいつでも、継承に対する議論の完璧な嵐があります:

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

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

質問は通常、組成vs.継承と表現されており、ここで質問されています前。

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

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

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

たとえば、何を達成したいのか、最初に何ができるのか、どのような制約がありますか

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

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

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

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

  1. is-a(継承)関係は、has-a(組成)関係よりも頻繁に発生しません。
  2. 継承を使用するのが適切であっても、継承を行うのは難しいため、カプセル化を破ったり、実装を公開するなどして密結合を促進したりする可能性があるため、デューデリジェンスを行う必要があります。

どちらもその場所を持っていますが、継承はより危険です。

もちろん、Shapeの「having-a」ポイントクラスとSquareクラスを作成することは意味がありません。ここでは、継承が原因です。

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

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

継承はここにありますか 海兵隊員の緑色のベレー帽<!> amp;狙撃兵。これらは兵士のタイプです。そのため、マリン、グリーンベレー<!> amp;を持つ基本クラスの兵士がいます。派生クラスとしてのスナイパー

ここに集約しますか ナップザックには手rena弾、銃(さまざまなタイプ)、ナイフ、メディキットなどを入れることができます。兵士はいつでもこれらのいずれかを装備することができます。傷害は一定の割合まで減少します。兵士クラスには、防弾チョッキクラスのオブジェクトと、これらのアイテムへの参照を含むナップザッククラスが含まれています。

両方の候補者が資格を得るとお気に入りが発生します。 AとBはオプションであり、Aを支持します。理由は、合成が一般化よりも拡張/柔軟性の可能性を提供するためです。この拡張性/柔軟性は、主に実行時/動的な柔軟性を指します。

メリットはすぐにはわかりません。メリットを確認するには、次の予期しない変更要求を待つ必要があります。そのため、ほとんどの場合、一般化にこだわる人は、構図を採用する人と比較すると失敗します(後述の明白な場合を除きます)。したがって、ルール。学習の観点から、依存性注入を正常に実装できる場合は、どの注入を優先するか、いつ行うかを知っておく必要があります。ルールは、意思決定にも役立ちます。よくわからない場合は、コンポジションを選択してください。

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

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

クラスが封印されているか、そうでなければインターセプトする必要がある非仮想メンバーがあるため、単一のクラスを集約する必要がある場合があります。プロキシを使用している場合、これにサブスクライブできるインターフェイスがあり、かなりうまく機能します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top