1387

なぜ継承よりも合成を好むのか各アプローチにはどのような妥協点がありますか?あなたはいつコンポジションよりも継承を選ぶべきですか?


  • また見なさいどのクラスデザインが優れているか - maccullt
  • その質問に関する良い記事がありますここに。私の個人的な意見では、「もっと良い」というものはありません。または「もっと悪い」設計の原則「適切」があります。 「不適切」具体的なタスクのための設計。言い換えれば - 私は状況に応じて、継承または合成の両方を使います。目標は、より小さなコードを作成し、読みやすく、再利用し、最終的にさらに拡張することです。 - m_pGladiator
  • パブリックメソッドを持っていてそれを変更した場合、継承はパブリックセンテンスを変更します。構成があり、構成されているオブジェクトが変更されている場合は、公開されているAPIを変更する必要はありません。 - Tomer Ben David
  • ウィキペディアで見る継承に関する構成。 - DavidRR

30 답변


1048

継承性よりも合成を優先します。後で柔軟にすることができ、後で変更するのが簡単ですが、常に構成するアプローチを使用しないでください。コンポジションを使用すると、Dependency Injection / Settersを使用してその場で動作を簡単に変更できます。ほとんどの言語では複数の型から派生させることができないため、継承はより厳密です。そのため、TypeAから派生したガチョウは、多かれ少なかれ調理されています。

上記の私の酸テストは:

  • TypeBは、TypeAが期待される場所でTypeBを使用できるように、TypeAの完全なインタフェース(すべてのパブリックメソッドはそれ以上)を公開したいですか?を示します継承

例えばCessna複葉機は、それ以上でなければ、航空機の完全なインターフェースを公開します。だからそれは飛行機から派生するのに適したものになります。

  • TypeBはTypeAによって公開された動作の一部または一部のみを望んでいますか?必要性を示します組成。

例えば鳥は飛行機の飛行行動のみを必要とするかもしれません。この場合、それをinterface / class / bothとして抽出し、それを両方のクラスのメンバーにすることは意味があります。

更新:ちょうど私の答えに戻ってきた、それは今バーバラリスコフの具体的な言及なしに不完全であるように思われるリスコフ代用原則「このタイプから継承する必要がありますか」のテストとして


  • 2番目の例は、Head First Design Patterns(amazon.com/First-Design-Patterns-Elisabeth-Freeman/dp/…私はこの質問をグーグルしていた人にその本を強くお勧めします。 - Jeshurun
  • これは非常に明確ですが、見落としがあるかもしれません。「TypeBがTypeAが想定される場所で使用できるように、TypeBはTypeAの完全なインターフェース(すべてのパブリックメソッドを含む)を公開しますか?」しかし、これが本当で、TypeBがTypeCの完全なインターフェースを公開しているとしたらどうでしょうか。 TypeCがまだモデル化されていない場合はどうなりますか。 - Tristan
  • あなたは私が最も基本的なテストであるべきだと思うことを暗示します:「このオブジェクトは(基本型であるだろう)のオブジェクトを期待するコードによって使用可能であるべきです」。答えがイエスなら、オブジェクトはしなければならない継承します。いいえの場合、おそらくそうではありません。私の指導者がいれば、言語は「このクラス」を参照するキーワードを提供し、他のクラスのように振る舞うべきであるが代用できないクラスを定義する手段を提供するでしょう。このクラスの参照はそれ自体に置き換えられます)。 - supercat
  • @Alexey - 重要なのは、'飛行機を期待しているすべてのお客様に、驚かずにCessna複葉機を渡すことができるかどうかを示します。'そうであれば、それから、あなたは相続がほしいと思う可能性があります。 - Gishu
  • 継承が私の答えであったであろう例について考えるのに実際に苦労しています、私はしばしば集約、構成およびインターフェースがより洗練された解決策をもたらすことを見つける。上記の例の多くは、これらのアプローチを使用してより適切に説明できます。 - Stuart Wakefield

357

封じ込めをがあります関係。車は「エンジン」、人は「名前」などを持っています。

継承をです関係。車は「車」、人は「哺乳類」などです。

私はこのアプローチには賛成ではありません。私はまっすぐからそれを取った第2版のコード補完によってスティーブマコネル第6.3節


  • これは常に完璧なアプローチではなく、単に良いガイドラインです。 Liskov Substitution Principleは、はるかに正確です(失敗は少なくなります)。 - Bill K
  • 「私の車には車があります」プログラミングの文脈ではなく、それを別の文として考えると、それはまったく意味がありません。そしてそれがこのテクニックの要点です。それが厄介に聞こえるなら、それはおそらく間違っています。 - Nick Zalutskiy
  • @Nickもちろん、"私の車はVehicleBehaviorを持っています"もっと理にかなっています(あなたの「Vehicle」クラスは「VehicleBehavior」という名前になると思います)。そのため、あなたは自分の決断を「持っている」に基づいて行動することはできません。 vs"は"です。比較、あなたはLSPを使わなければなりません、さもなければあなたは間違いを犯すでしょう - Tristan
  • "の代わりに" 「のように動作する」と考えてください。継承とは、意味だけでなく、振る舞いを継承することです。 - ybakos
  • @ybakos"動作"継承を必要とせずにインターフェイスを介して実現できます。ウィキペディアから「継承に対する合成の実装は、通常、システムが示さなければならない動作を表すさまざまなインタフェースの作成から始まります。したがって、システム動作は継承なしで実現されます。」 - DavidRR

184

違いを理解していれば、説明する方が簡単です。

手続きコード

この例は、クラスを使用していないPHPです(特にPHP 5以前)。すべてのロジックは一連の関数にエンコードされています。ヘルパー関数などを含む他のファイルをインクルードし、関数にデータを渡してビジネスロジックを実行することができます。アプリケーションが大きくなるにつれて、これを管理するのは非常に困難になります。 PHP5はもっとオブジェクト指向のデザインを提供することでこれを解決しようとします。

継承

これはクラスの使用を促進します。継承はオブジェクト指向デザインの3つの原則の1つです(継承、多態性、カプセル化)。

class Person {
   String Title;
   String Name;
   Int Age
}

class Employee : Person {
   Int Salary;
   String Title;
}

これは職場での継承です。従業員は「人」であるか、または人から継承します。すべての継承関係は "is-a"関係です。 Employeeは、PersonからTitleプロパティを隠します。つまり、Employee.Titleは、Personではなく、EmployeeのTitleを返します。

組成

構成は継承よりも優先されます。簡単に言えば、次のようになります。

class Person {
   String Title;
   String Name;
   Int Age;

   public Person(String title, String name, String age) {
      this.Title = title;
      this.Name = name;
      this.Age = age;
   }

}

class Employee {
   Int Salary;
   private Person person;

   public Employee(Person p, Int salary) {
       this.person = p;
       this.Salary = salary;
   }
}

Person johnny = new Person ("Mr.", "John", 25);
Employee john = new Employee (johnny, 50000);

コンポジションは、通常「持つ」または「使う」という関係です。ここでEmployeeクラスはPersonを持ちます。 Personから継承するのではなく、代わりにPersonオブジェクトを渡します。これが、Personを「持つ」理由です。

継承に関する構成

それでは、Managerタイプを作成したいとしましょう。

class Manager : Person, Employee {
   ...
}

この例は問題なく動作しますが、PersonとEmployeeの両方が宣言した場合はどうなりますか。Title? Manager.Titleは "Manager of Operations"と "Mr."のどちらを返すべきですか?構成上、このあいまいさはより適切に処理されます。

Class Manager {
   public string Title;
   public Manager(Person p, Employee e)
   {
      this.Title = e.Title;
   }
}

Managerオブジェクトは、EmployeeとPersonとして構成されています。役職の動作は従業員から取得されます。この明示的な構成は、とりわけあいまいさを排除し、あなたはより少ないバグに遭遇するでしょう。


  • 継承の場合:あいまいさはありません。要件に基づいてManagerクラスを実装しています。したがって、"運用管理者"を返します。それがあなたの要求が指定したものであるならば、そうでなければあなたはただベースクラスの実装を使うでしょう。また、Personを抽象クラスにして、下流のクラスがTitleプロパティを確実に実装するようにすることもできます。 - Raj Rao
  • 覚えておくことが重要ですが、「継承よりも合成」と言うことがあります。しかし、それは「コンポジションを常に継承する」という意味ではありません。 " Is"継承を意味し、コードの再利用につながります。従業員は人です(従業員には人がいません)。 - Raj Rao
  • 例は混乱しています。従業員は人なので、継承を使用する必要があります。技術的にはコードで宣言できても、ドメインモデルでは間違った関係になっているため、この例では構成を使用しないでください。 - Michael Freidgeim
  • 私はこの例に同意しません。従業員です人、継承の適切な使用の教科書ケースです。 " issue"も問題だと思います。 Titleフィールドの再定義は意味がありません。 Employee.TitleがPerson.Titleを影にしているという事実は、貧弱なプログラミングの兆候です。結局のところ、 "Mr."です。 「運用管理者」実際に人の同じ側面(小文字)を参照していますか?私はEmployee.Titleという名前に変更し、それによってEmployeeのTitle属性とJobTitle属性を参照できるようになります。これらはどちらも現実的には意味があります。さらに、Managerに理由はありません(続き...) - Radon Rosborough
  • (...続き)PersonとEmployeeの両方から継承するには - 結局、EmployeeはすでにPersonから継承しています。人がマネージャやエージェントになるかもしれないもっと複雑なモデルでは、多重継承を使うことができるのは事実です(慎重に!)が、マネージャ(従業員を含む)から抽象ロールクラスを持つことは多くの環境で好ましいでしょう彼/彼女が管理する)およびエージェント(契約およびその他の情報を含む)は継承します。それから、従業員は複数のロールを持つ人です。したがって、構成と継承の両方が適切に使用されます。 - Radon Rosborough

98

継承によって提供される否定できない利点はすべて、ここにいくつかの欠点があります。

継承のデメリット:

  1. 実行時にスーパークラスから継承した実装を変更することはできません(継承はコンパイル時に定義されるため、明らかに)。
  2. 継承はサブクラスをその親のクラス実装の詳細にさらすので、継承はカプセル化を破るとよく言われます(実際には実装ではなくインタフェースだけに集中する必要があるという意味で、サブクラス化による再利用は常に好まれるわけではありません)。
  3. 継承によって提供される密結合は、サブクラスの実装をスーパークラスの実装と非常に密接に結び付け、親の実装に何らかの変更があるとサブクラスにも変更を強いることになります。
  4. サブクラス化によって過度に再利用すると、継承スタックが非常に深くなり、非常に混乱しやすくなります。

一方オブジェクト構成実行時に、他のオブジェクトへの参照を取得するオブジェクトを通じて定義されます。そのような場合、これらのオブジェクトは互いの保護されたデータに到達することができず(カプセル化の中断がない)、互いのインターフェースを尊重することを余儀なくされます。そしてこの場合も、実装の依存関係は継承の場合よりもずっと少なくなります。


  • 私の考えでは、これはより良い答えの1つです。私の経験では、構成に関してあなたの問題を再考しようとすると、より小さく、より単純で、より自己充足的でより再利用可能なクラスにつながる傾向があります。より明確で、より小さく、より焦点を絞った責任範囲があります。多くの場合、これは、依存性の注入や(テストでの)モックのようなものが、より小さなコンポーネントでも独立して立つことができるため、必要性が少なくなることを意味します。私の経験だけです。 YMMV :-) - mindplay.dk
  • この記事の最後の段落は本当に私のためにクリックしました。ありがとうございました。 - Salx
  • ベストアンサー、ハンズダウン - bertie

77

継承よりも合成を好むもう1つの非常に実用的な理由は、ドメインモデルとそれをリレーショナルデータベースにマッピングすることです。継承をSQLモデルにマッピングするのは本当に難しいです(あなたは結局いつも使用されるわけではないカラムを作成すること、ビューを使用することなどのようなあらゆる種類の厄介な回避策になります)。 ORMLの中にはこれを処理しようとするものがありますが、それは常に急速に複雑になります。コンポジションは2つのテーブル間の外部キー関係によって簡単にモデル化できますが、継承ははるかに困難です。


69

一言で言えば、私は「継承よりもコンポジションを好む」に同意するでしょうが、私にとっては「コカコーラよりもポテトを好む」のように聞こえます。継承のための場所と構成のための場所があります。あなたは違いを理解する必要があります、そしてこの質問は消えます。それが私にとって本当に意味することは「あなたが継承を使うつもりならば - もう一度考えてください、あなたは構成が必要であるということです」。

あなたが食べたいときにはコカコーラよりポテトを好み、あなたが飲みたいときにはポテトよりコカコーラを選ぶべきです。

サブクラスを作成することは、スーパークラスのメソッドを呼び出すための単なる便利な方法以上の意味を持つはずです。構造的にも機能的にもサブクラスの「スーパークラス」である場合は継承を使用します。スーパークラスとして使用できる場合はそれを使用します。そうでない場合 - それは継承ではなく、他の何かです。コンポジションとは、あなたのオブジェクトが他のオブジェクトで構成されているか、またはそれらと何らかの関係がある場合です。

だから私にとっては、誰かが自分が相続や構成を必要としているかどうかわからないのであれば、本当の問題は彼が彼が飲みたいのか食べたいのかわからないということです。あなたの問題領域についてもっと考えて、もっとよく理解してください。


  • 適切な仕事のための適切なツール。ハンマーはレンチよりも物を打つのが得意かもしれませんが、レンチを「劣ったハンマー」と見なすべきではありません。サブクラスに追加されたものがスーパークラスオブジェクトとして動作するためにオブジェクトに必要な場合、継承は役に立ちます。たとえば、基本クラスを考えてみましょうInternalCombustionEngine派生クラスを使ってGasolineEngine。後者は、基本クラスにはないスパークプラグのようなものを追加しますが、それをInternalCombustionEngineスパークプラグが使用される原因となります。 - supercat

54

継承は、特に手続き型の土地から来るとかなり魅力的であり、それはしばしば一見エレガントに見えます。私がしなければならないのは、この1ビットの機能を他のクラスに追加することだけです。まあ、問題の一つはそれです

継承はおそらくあなたが持つことができるカップリングの最悪の形です

基本クラスは、実装の詳細を保護されたメンバーの形式でサブクラスに公開することによってカプセル化を破ります。これはあなたのシステムを堅くそして脆弱にします。しかしながら、より悲劇的な欠陥は、新しいサブクラスがそれにすべての手荷物と継承チェーンの意見をもたらすということです。

記事、継承は悪です:DataAnnotationsModelBinderの壮大な失敗、C#でこの例を介して歩きます。それは、コンポジションが使用されるべきであるときの継承の使用と、それがどのようにリファクタリングされることができるかを示します。


  • 継承は良くも悪くもありません、それは単にコンポジションの特別なケースです。実際、サブクラスはスーパークラスと同様の機能を実装しています。提案されたサブクラスが再実装ではなく単に使うスーパークラスの機能では、継承を誤って使用しています。これはプログラマーの間違いであり、継承についての考察ではありません。 - iPherian

39

JavaまたはC#では、インスタンス化されたオブジェクトはその型を変更できません。

そのため、オブジェクトが別のオブジェクトとして表示される必要がある場合や、オブジェクトの状態や条件に応じて動作が異なる場合は、次のようにします。組成: 参照する状態そして戦略デザインパターン。

オブジェクトが同じ型である必要がある場合は、継承あるいはインターフェースを実装する。


  • +1ほとんどの状況で継承が機能することがますますわかっていません。私は共有/継承されたインターフェースとオブジェクトの構成を好みます。それとも集約と呼ばれますか?聞かないでください、私はEEの学位を取得しました。 - kenny
  • これは、「継承に対する構成」が最も一般的なシナリオであると考えています。どちらも理論的に当てはまる可能性があるため、適用されます。たとえば、マーケティングシステムでは、次のような概念があります。Client。それから、の新しい概念PreferredClient後でポップアップします。すべきPreferredClient受け継ぐClient?優先クライアントは'です。クライアントafternl、いいえ?それほど速くはありませんが、実行時にオブジェクトがクラスを変更することはできません。どのようにモデル化しますかclient.makePreferred()操作?おそらく答えは、欠けている概念、Accountおそらく? - plalx
  • 違うタイプではなくClientクラスの概念をカプセル化したクラスが1つだけAccountこれはStandardAccountまたはPreferredAccount... - plalx

29

私は個人的には継承よりも合成を好むことを学びました。コンポジションでは解決できない、継承で解決できるプログラム上の問題はありません。ただし、場合によっては、インタフェース(Java)またはプロトコル(Obj-C)を使用する必要があります。 C ++はそのようなことを何も知らないので、抽象基底クラスを使わなければなりません。つまり、C ++では継承を完全に取り除くことはできません。

コンポジションは論理的であることが多く、抽象化、カプセル化、コードの再利用を改善し(特に大規模プロジェクトで)、コード内のどこかに独立した変更を加えたからといって遠く離れてもうまくいきません。それはまたそれを容易に支持することを可能にします」単一責任原則「これはよく要約される」クラスが変更される理由が複数あることはありません。「そして、すべてのクラスは特定の目的のために存在し、その目的に直接関連するメソッドのみを持つべきであることを意味します。多くの人が、継承は私たちの現実の世界それは実はよくありません。現実の世界は、継承よりもはるかに多くの構成を使用しています。あなたがあなたの手に持つことができるほとんどすべての現実世界のオブジェクトは他のより小さな現実世界のオブジェクトから構成されています。

ただし、構成には欠点があります。継承を完全にスキップして合成のみに焦点を当てると、継承を使用していた場合には不要だった余分なコード行を2、3行作成しなければならないことに気づくでしょう。あなたはまた時々自分自身を繰り返すことを余儀なくされ、これはDRYの原則(DRY =自分自身を繰り返さないでください)。また、コンポジションはしばしば委任を必要とし、メソッドはこの呼び出しを囲む他のコードなしで他のオブジェクトの他のメソッドを呼び出すだけです。そのような「二重メソッド呼び出し」(これは、三重または四重メソッド呼び出しに拡張することができ、それよりはるかに遠いこともあります)は、単純に親のメソッドを継承する継承よりもはるかにパフォーマンスが悪くなります。継承されたメソッドを呼び出すことは、継承されていないメソッドを呼び出すことと同じくらい速いかもしれませんし、あるいは少し遅いかもしれませんが、通常は2つの連続したメソッド呼び出しより速いです。

あなたは、ほとんどのオブジェクト指向言語が多重継承を許さないことに気づいたかもしれません。多重継承が本当にあなたに何かを買うことができるケースがいくつかありますが、それらはルールよりもむしろ例外です。 「多重継承はこの問題を解決するための本当にクールな機能だ」と思うような状況に遭遇したときはいつでも、継承を完全に再考する必要がある時点にいます。コンポジションをベースにしたソリューションは、通常、はるかにエレガントで柔軟性があり、将来性のある証明になります。

継承は本当にクールな機能ですが、ここ数年で使われ過ぎてきたようです。それが実際には釘なのか、ネジなのか、あるいはまったく違うものなのかにかかわらず、人々はそれをすべて釘付けにすることができる一つのハンマーとして継承を扱いました。


29

ここに満足のいく答えが見つからなかったので、私は新しいものを書きました。

その理由を理解するために」好む"継承の上の構成"、私たちは最初にこの短縮された慣用句で省略された仮定を取り戻す必要があります。

継承には2つの利点があります。サブタイプとサブクラス

  1. サブタイピングタイプ(インターフェース)シグニチャ、すなわち一組のAPIに準拠することを意味し、サブタイプポリモーフィズムを達成するためにシグニチャの一部をオーバーライドすることができる。

  2. サブクラス化メソッド実装の暗黙の再利用を意味します。

この2つの利点には、継承を実行するための2つの異なる目的があります。サブタイプ指向とコード再利用指向です。

コードの再利用が唯一つまり、親クラスのパブリックメソッドの中には、子クラスにとってあまり意味がないものがあります。この場合、継承よりも合成を優先するのではなく、合成は要求された。これはまた、 "is-a"対 "has-a"の概念が由来するところです。

したがって、サブタイプが意図されている場合、つまり、後で新しいクラスを多態的に使用する場合にのみ、継承または合成を選択するという問題に直面します。これは、議論中の短縮された慣用句では省略されるという仮定です。

サブタイプとは型シグネチャに準拠することです。これは、コンポジションが常にその型のAPIをそれ以上公開しないことを意味します。これでトレードオフが始まります。

  1. オーバーライドされていない場合、継承は単純なコードの再利用を提供しますが、単純な委任の仕事であっても、コンポジションはすべてのAPIを再コーディングする必要があります。

  2. 継承は直接的なものですオープン再帰内部多型サイト経由thisつまり、オーバーライドメソッドを呼び出す(あるいはタイプ)パブリックまたはプライベートの別のメンバー関数でがっかり)オープン再帰は合成によるシミュレーションしかし、それは余分な労力を必要とし、常に実行可能とは限りません(?)。この回答重複した質問に似たようなことを話します。

  3. 継承の公開保護されたメンバーこれは親クラスのカプセル化を破り、サブクラスで使用されている場合は、子とその親の間に別の依存関係が導入されます。

  4. コンポジションは制御の逆転という利点があり、その依存関係は次のように動的に注入できます。デコレータパターンそしてプロキシパターン

  5. コンポジションにはコンビネータ指向プログラミング、すなわち、複合パターン

  6. 直後に構図インタフェースへのプログラミング

  7. 構成は簡単の利点があります多重継承

上記のトレードオフを念頭に置いて、好む相続以上の構成密接に関連したクラス、つまり暗黙のコード再利用が本当に利益をもたらす場合、またはオープン再帰の魔法の力が望まれる場合は、継承が選択となります。


  • 良い答え、もっと+1するに値する。 - markus
  • ここに満足のいく答えが見つからなかったので、あなたはそれを書きました。 - leokhorn

20

私の一般的な経験則:継承を使用する前に、合成がより意味があるかどうかを検討してください。

理由:サブクラス化は通常、より複雑さと接続性を意味します。つまり、間違いを犯さずに変更、維持、および拡張を行うのは困難です。

より完全で具体的なTim Boudreauからの回答太陽の

私が見ているような継承の使用に関する一般的な問題は次のとおりです。

  • 罪のない行為は予想外の結果をもたらす可能性があります - この典型的な例は、スーパークラスからオーバーライド可能なメソッドへの呼び出しです。   サブクラスインスタンスフィールドの前のコンストラクタ   初期化されました。完璧な世界では、だれもそれをしないでしょう。これは   完璧な世界ではありません。
  • それはサブクラスがメソッド呼び出しの順序などについて仮定を立てるというひどい誘惑を提供します - そのような仮定はしない傾向がある   スーパークラスが時間の経過とともに進化する可能性がある場合は安定している必要があります。また見なさい私のトースター   とコーヒーポットの例え
  • クラスが重くなる - あなたは、あなたのスーパークラスがそのコンストラクタでどんな仕事をしているのか、あるいはそれがどれくらいのメモリを行っているのかを必ずしも知らない。   使用する。そのため、いくつかの無実の軽量オブジェクトを構築することができます。   あなたが思うよりはるかに高価である、そしてこれは時間の経過とともに変わるかもしれない   スーパークラスが進化する
  • それはサブクラスの爆発を促進します。クラスローディングは時間がかかり、クラスを増やすとメモリがかかります。あなたがするまでこれは問題ないかもしれません   NetBeansの規模のアプリケーションを扱うことができました   たとえば、最初の表示が遅いためにメニューが遅くなるなどの問題   メニューが大量のクラスロードを引き起こした。に移動することでこれを修正しました   より宣言的な構文や他のテクニックが、それには時間がかかります   同様に修正してください。
  • それは後で物事を変えることを難しくします - あなたがクラスをパブリックにした場合、スーパークラスを交換するとサブクラスが壊れます -   それは、あなたがコードを公開したら、あなたは結婚しているという選択です。   に。実際の機能を自分のものに変えていないのであれば   スーパークラスであれば、後で自由に変更することができます。   必要なものを拡張するのではなく、使用してください。例えば、   JPanelのサブクラス化 - これは通常間違っています。そしてサブクラスが   どこかで公の場では、あなたはその決定を再検討する機会を得ることはありません。もし   それはJComponent getThePanel()としてアクセスされます、あなたはまだそれをすることができます(ヒント:   APIとしてのコンポーネントのモデルを公開します。
  • オブジェクト階層は拡張されません(または、後で拡張することは、前もって計画するよりもはるかに困難です)。 - これは古典的な「多すぎるレイヤー」です   問題。以下に、そしてAskTheOracleパターンがどのようにできるのかを説明します。   それを解決する(それはOOP純粋主義者を害するかもしれないが)。

...

継承を許可している場合はどうすればよいですか。   塩の粒と一緒に取る:

  • 定数以外はフィールドを公開しない
  • 方法は抽象的または最終的でなければならない
  • スーパークラスのコンストラクタからメソッドを呼び出さない

...

これはすべて、大規模プロジェクトよりも小規模プロジェクトにはあまり当てはまりません。   パブリッククラスよりプライベートクラスへ


17

継承は非常に強力ですが、強制することはできません(参照:円 - 楕円問題)本当の「is-a」サブタイプの関係を完全に確信できない場合は、構成を使用するのが最善です。


14

継承はサブクラスとスーパークラスの間に強い関係を作ります。サブクラスはスーパークラスの実装の詳細を知っていなければなりません。スーパークラスをどのように拡張できるかを考えなければならない場合、スーパークラスを作成するのははるかに困難です。クラスの不変式を慎重に文書化し、他のオーバーライド可能なメソッドが内部で使用しているものを説明する必要があります。

階層が実際にis-a-relationを表している場合、継承は時々役に立ちます。それは、クラスは修正のためにクローズされるべきであるが拡張のためにオープンであるべきであると述べるオープンクローズド原理に関連します。そうすれば、多態性を持つことができます。スーパータイプとそのメソッドを扱う一般的なメソッドを持つために、動的なディスパッチを通してサブクラスのメソッドが呼び出されます。これは柔軟性があり、(実装の詳細についてあまり知らないために)ソフトウェアに不可欠な間接指定を作成するのに役立ちます。

ただし、継承は簡単に使いすぎになり、クラス間の依存関係が厳しくなり、複雑さが増します。また、プログラムの実行中に何が起こるかを理解することは、レイヤとメソッド呼び出しの動的選択のためにかなり困難になります。

デフォルトとして構成を使用することをお勧めします。よりモジュール化されており、遅延バインディングの利点があります(コンポーネントを動的に変更できます)。また、個別にテストする方が簡単です。そして、もしあなたがクラスからのメソッドを使う必要があるなら、あなたは特定の形式になることを強制されません(Liskov Substitution Principle)。


  • 継承が多態性を実現する唯一の方法ではないことに注目する価値があります。デコレータパターンは、構成を通じて多型の外観を提供します。 - BitMask777
  • @ BitMask777:サブタイプ多型は一種の多型であり、もう一つはパラメトリック多型であり、そのための継承は必要ありません。さらに重要なことは、継承について話すとき、クラス継承を意味します。 。複数のクラスに共通のインターフェイスを使用することでサブタイプ多型を持つことができますが、継承の問題は発生しません。 - egaga
  • @engaga:あなたのコメントを解釈しましたInheritance is sometimes useful... That way you can have polymorphism継承とポリモーフィズムの概念をハードリンクする(サブタイプはコンテキストを前提としていると仮定)。私のコメントは、あなたが自分のコメントで明確にしていることを指摘することを意図していました。つまり、継承は多態性を実装する唯一の方法ではなく、実際にコンポジションと継承を決定するときの決定要因ではありません。 - BitMask777

13

ご覧になる必要がありますリスコフ代用原則アンクルボブ固体クラスデザインの原則:)


13

なぜ継承よりも合成を好むのか

他の答えを見てください。

あなたはいつコンポジションよりも継承を選ぶべきですか?

いつでも文「バーはFooです、そしてバーはFooができることすべてをすることができます」理にかなっています。

「a Bar is a Foo」という文が意味を成すのであれば、継承を選択することが適切であることは良いヒントであると従来の知恵は言います。たとえば、犬は動物なので、DogクラスにAnimalを継承させるのはおそらく良い設計です。

残念ながら、この単純な「is-a」テストは信頼できません。の円楕円問題たとえば、円が楕円形であっても、CircleクラスをEllipseから継承することはお勧めできません。楕円形ではできるが円ではできないことがあるからです。たとえば、楕円は伸びることができますが、円は伸びることができません。だからあなたが持つことができる間ellipse.stretch()、持っていることはできませんcircle.stretch()

これが、より良いテストが「バーはFooです、そしてBarはFooができることすべてをすることができます"これは本当にFooが多態的に使われることができることを意味します。" is-a "テストは多態的な使用に必要な条件であり、通常はFooのすべてのゲッターがBarで意味を成すことを意味します。この追加のテストは通常、クラスBar "is-a" Fooのときに失敗しますが、いくつかの制約が追加されます。この場合、Fooは継承できないため、継承を使用しないでください。言い換えれば、継承はプロパティの共有ではなく、機能の共有に関するものです。伸びる基本クラスの機能は制限しません。

これは、リスコフ代用原則

基本クラスへのポインタまたは参照を使用する関数は、それを知らなくても派生クラスのオブジェクトを使用できなければなりません。


  • 円 - 楕円と四角形 - 長方形のシナリオは悪い例です。サブクラスは常にそのスーパークラスよりも複雑です。そのため、問題が生じます。この問題は、関係を逆にすることで解決されます。楕円は円から派生し、長方形は正方形から派生します。これらのシナリオでコンポジションを使用するのは非常にばかげています。 - Fuzzy Logic
  • @FuzzyLogicは同意しました、しかし実際には、私の投稿はこのケースでコンポジションを使用することを主張していません。円と楕円の問題が、なぜ「is-a」であるかの良い例であるとだけ言った。 CircleはEllipseから派生するべきだと結論づけるための単独のテストではありません。実際には、CircleはLSPの違反のためにEllipseから派生するべきではないと考えられるので、考えられる選択肢は、関係を反転するか、合成を使用するか、テンプレートクラスを使用するか、追加のクラスやヘルパーを含むより複雑なデザインを使用することです。関数など、そして決定は明らかにケースバイケースでとられるべきです。 - Boris
  • @FuzzyLogicそして、Circle-Ellipseの特定のケースについて私が主張することに興味があるなら、Circleクラスを実装しないことを主張します。関係を逆転させることに関する問題は、それがLSPにも違反するということです。関数を想像してください。computeArea(Circle* c) { return pi * square(c->radius()); }。楕円を渡すと明らかに壊れています(radius()はどういう意味ですか?)楕円は円ではないため、円から派生してはいけません。 - Boris
  • computeArea(Circle *c) { return pi * width * height / 4.0; }現在は一般的です。 - Fuzzy Logic
  • @FuzzyLogic私は同意しません。これは、Circleクラスが派生クラスEllipseの存在を予期していたために提供されたものであることを意味しています。width()そしてheight()?今、図書館利用者が" EggShape"という別のクラスを作成することにした場合はどうなりますか。 「Circle」からも派生するはずですか。もちろん違います。卵形は円ではなく、楕円形も円ではありません。そのためLSPを破るのでCircleから派生するものはありません。 Circle *クラスで演算を実行するメソッドは、円とは何かについて強い仮定を立てています。これらの仮定を破ると、ほぼ間違いなくバグが発生します。 - Boris

12

航空機には、エンジンと翼の2つの部品しかないとします。

それから航空機クラスを設計する2つの方法があります。

Class Aircraft extends Engine{
  var wings;
}

今あなたの航空機は固定翼を持つことから始めることができます

そしてそれらをその場で回転翼に変える。それは本質的に

翼を持つエンジン。しかし、私が変更したい場合はどうなりますか

エンジンもその場で?

どちらかの基本クラスEngine変更するミューテーターを公開する

プロパティ、または私は再設計Aircraftとして:

Class Aircraft {
  var wings;
  var engine;
}

これで、エンジンをその場で交換することができます。


  • あなたの投稿は私が今まで考えていなかった点を思い出させます - 銃器のようなものの上にあなたの複数の部分を持つあなたの機械的な物のアナロジーを続けるために、一般的に全体として銃器のそれであるために(拳銃のために、それは典型的にはフレームでしょう)。他のすべての部品を交換しても同じ銃を持っているかもしれませんが、フレームにひびが入って交換する必要がある場合、元のガンの他のすべての部品と新しいフレームを組み立てた結果は新しいガンになります。ご了承ください... - supercat
  • ...砲の複数の部分に刻印されたシリアル番号があるという事実は、砲が複数の識別情報を持つことができるという意味ではありません。フレームのシリアル番号だけがガンを識別します。他の部品のシリアル番号は、それらの部品がどのガンと一緒に組み立てられるように製造されたかを識別します。 - supercat

9

「継承よりも合成を優先する」という設計原則は、継承が適切でない場合には継承を悪用しないでください。

2つのエンティティ間に実世界の階層関係が存在しない場合は、継承を使用せず、代わりに合成を使用してください。構成は「HAS A」の関係を表します。

例えば。車には車輪、車体、エンジンなどがあります。ただし、ここで継承している場合、車は車輪になります - これは誤りです。

詳しくは、を参照してください。継承よりも合成を好む


  • 通常、悪の核心は単に「車は車輪」ではありません。 "車は四輪、ステアリングホイール、窓、車体、座席などのあるものです。通常、これは他のすべての車のための全能の神父カーであるBaseCarで終わります。あなたは典型的ではないいくつかの車が必要になるまで、すべてがうまく動作します。ちょうど3つの車輪が付いている車かハンドルがない自動車。そのとき初めてあなたは、おそらくそこにあるすべての車にすべての可能な機能を強制することはそれほど素晴らしい考えではなかったことに気づくでしょう。 - JustAMartin
  • 犬や犬に関するものが良いものではないのと同じ理由で、良い答えではありません。車の問題は構図でもより確実に解決できます。 BMWの代わりにBWMがcarを実装し、carを拡張します。 - markus

6

基本クラスのAPIを「コピー」/公開したい場合は、継承を使用します。機能を「コピー」するだけの場合は、委任を使用してください。

この一例としては、リストからスタックを作成したいとします。スタックはポップ、プッシュ、ピークのみです。 push_back、push_front、removeAtなどのような機能をスタックに入れたくない場合は、継承を使用しないでください。


6

これら2つの方法は、うまく共存し、実際に互いに支え合うことができます。

コンポジションは単にモジュール式に再生されます。親クラスに似たインターフェースを作成し、新しいオブジェクトを作成し、それに呼び出しを委任します。これらのオブジェクトが互いを知る必要がない場合は、非常に安全で使いやすいコンポジションです。ここにはたくさんの可能性があります。

しかし、何らかの理由で親クラスが経験の浅いプログラマ用の「子クラス」によって提供される関数にアクセスする必要がある場合は、継承を使用するのに最適な場所のように見えます。親クラスは、サブクラスによって上書きされるそれ自身の抽象 "foo()"を呼び出すだけで、抽象基底に値を渡すことができます。

これはいい考えのように見えますが、多くの場合、単に必要なベースクラスから新しいクラスを継承するよりも、単にfoo()を実装するオブジェクトを(またはfoo()から提供される値を手動で設定する)オブジェクトに渡すほうが良いです。指定する関数foo()

どうして?

継承は情報を移動するための悪い方法です

コンポジションはここで本当の強みを持っています:関係は逆にすることができます: "親クラス"または "抽象ワーカー"は特定のインターフェースを実装する特定の "子"オブジェクトを集約することができます+どんな子でも他のどの種類の親の中にも設定できます。。また、MergeSortやQuickSortは抽象比較インターフェイスを実装するオブジェクトのリストを並べ替えることができます。言い換えれば、 "foo()"を実装しているオブジェクトのグループと、 "foo()"を持っているオブジェクトを利用できる他のオブジェクトのグループは、一緒にプレイすることができます。

継承を使用する3つの本当の理由を考えることができます。

  1. たくさんのクラスがあります同じインターフェースそして、あなたはそれらを書く時間を節約したいです
  2. 各オブジェクトに同じ基本クラスを使用する必要があります
  3. あなたはプライベート変数を変更する必要があります。

もしこれらが真実なら、おそらく継承を使う必要があるでしょう。

理由1を使用して悪いことは何もありません、それはあなたのオブジェクトのしっかりしたインターフェースを持つことは非常に良いことです。これは、コンポジションを使用しても継承を使用しても実行できます。このインターフェイスが単純で変更されない場合は問題ありません。通常、継承はここではかなり効果的です。

理由が2の場合、それは少しトリッキーになります。本当に同じ基本クラスを使うだけでいいのですか?一般に、同じ基本クラスを使用するだけでは十分ではありませんが、それはあなたのフレームワークの要件であり、避けられない設計上の考慮事項です。

しかし、あなたがプライベート変数を使いたい場合、ケース3、そしてあなたは問題を抱えているかもしれません。グローバル変数を安全でないと考えるなら、プライベート変数へのアクセスを安全にするために継承を使うことを考えるべきです。。ご存知のとおり、グローバル変数はすべて悪いわけではありません - データベースは本質的にグローバル変数の大きなセットです。しかし、あなたがそれを扱うことができれば、それはそれで結構です。


5

それ以外にも考慮すべき点があります、あなたのオブジェクトが通過しなければならない継承の「深さ」も考慮しなければなりません。継承レベルが5または6レベルを超えると、予期しないキャストやボクシング/アンボクシングの問題が発生する可能性があります。その場合は、代わりにオブジェクトを作成することをお勧めします。


5

これを理解する簡単な方法は、同じクラスを持つクラスのオブジェクトが必要なときに継承を使うべきだということです。インタフェースそのため、その親クラスとして、親クラスのオブジェクトとして扱うことができます(アップキャスト)。さらに、派生クラスオブジェクトの関数呼び出しはコード内のどこでも同じままですが、呼び出す特定のメソッドは実行時に決定されます(つまり、低レベルの実装異なる、高レベルインタフェース同じまま)。

新しいクラスが同じインターフェースを持つ必要がない場合、つまりそのクラスのユーザーが知っている必要がないクラスの実装の特定の側面を隠したい場合は、コンポジションを使用する必要があります。そのため、構成はサポートの方がよりカプセル化継承はサポートを目的としていますが(つまり、実装を隠す)抽象化(つまり、何かの簡略化された表現、この場合は同じ内部構造が異なるさまざまなタイプのインタフェース)


  • インターフェースについては+1。私はこのアプローチをよく使って既存のクラスを隠し、新しいクラスを適切にユニットテスト可能にします。これは、新しいオブジェクトの所有者が代わりに候補の親クラスを渡すことを要求します。 - Kell

5

あなたが持っているときです2つのクラスの間の関係(例犬は犬です)、あなたは相続のために行きます。

一方であなたが持っているときがありますまたは2つのクラス(学生がコースを持っている)または(教師研究コース)間の何らかの形容詞の関係、あなたは作文を選びました。


  • あなたは継承と継承を言いました。あなたが相続や構成を意味していませんか? - trevorKirkby
  • いいえ、しません。犬用のインターフェースを定義して、すべての犬にそれを実装させることもできます。そうすると、より多くのSOLIDコードが表示されるようになります。 - markus

4

サブタイピングは適切で強力な場合はより強力です。不変式を列挙することができますそうでなければ、拡張性のために関数合成を使います。


4

@Pavelに同意する、彼が言うとき、構成のための場所があり、継承のための場所がある。

あなたの答えがこれらの質問のどれにでも肯定的であるならば、私は相続が使われるべきであると思います。

  • あなたのクラスはポリモーフィズムの恩恵を受ける構造の一部ですか?たとえば、draw()というメソッドを宣言するShapeクラスがある場合、そのクライアントクラスが特定のサブクラスではなくShapeに依存するように、CircleクラスとSquareクラスをShapeのサブクラスにする必要があります。
  • あなたのクラスは他のクラスで定義されている高レベルのインタラクションを再利用する必要がありますか?のテンプレート方式設計パターンは、継承なしには実装できません。私はすべての拡張可能なフレームワークがこのパターンを使っていると思います。

ただし、純粋にコードを再利用することを意図しているのであれば、合成はおそらくより良い設計上の選択です。


3

私が聞いたことのある経験則では、継承は「ある」関係であるときは継承を使用し、「ある」ときは構成を使用する必要があります。それでも、私はあなたが常に作曲に傾くべきであると感じます。


2

継承はコードの再利用にとって非常に強力なメカニズムです。しかし、適切に使用する必要があります。サブクラスが親クラスのサブタイプでもある場合、継承は正しく使用されていると言えます。前述のように、ここでの重要なポイントはLiskov Substitution Principleです。

サブクラスはサブタイプと同じではありません。あなたはサブタイプではないサブクラスを作成するかもしれません(そしてこれはあなたがコンポジションを使うべき時です)。サブタイプが何であるかを理解するために、タイプが何であるかの説明を始めましょう。

数5が整数型であると言うときは、5が一連の可能な値に属していることを示しています(例として、Javaプリミティブ型の可能な値を参照してください)。足し算や引き算のような値に対して実行できる有効な方法のセットがあることも述べています。そして最後に、常に満足できる一連のプロパティがあることを述べています。たとえば、値3と5を追加すると、結果として8が得られます。

別の例を挙げると、抽象データ型であるSet of integerとList of integersについて考えてみましょう。それらが保持できる値は整数に制限されています。どちらもadd(newValue)やsize()のような一連のメソッドをサポートしています。そして、それらは両方とも異なるプロパティを持ち(クラス不変)、Listは複製を許可しますが、Setは複製を許可しません(もちろん、両方が満たす他の属性もあります)。

サブタイプは、親タイプ(またはスーパータイプ)と呼ばれる別のタイプとの関係を持つタイプでもあります。サブタイプは、親タイプの機能(値、メソッド、およびプロパティー)を満たす必要があります。この関係は、スーパータイプが期待されるいかなる状況においても、実行の振る舞いに影響を与えることなく、それがサブタイプによって置換可能であることを意味します。私が言っていることを例示するためのコードを見に行きましょう。整数のリストを(ある種の疑似言語で)書くとします。

class List {
  data = new Array();

  Integer size() {
    return data.length;
  }

  add(Integer anInteger) {
    data[data.length] = anInteger;
  }
}

それから、整数の集合を整数のリストのサブクラスとして書きます。

class Set, inheriting from: List {
  add(Integer anInteger) {
     if (data.notContains(anInteger)) {
       super.add(anInteger);
     }
  }
}

Set of integersクラスは、List of Integersのサブクラスですが、Listクラスのすべての機能を満たしているわけではないため、サブタイプではありません。メソッドの値とシグネチャは満たされていますが、プロパティは満たされていません。 add(Integer)メソッドの動作は、親型のプロパティを保持するのではなく、明確に変更されました。あなたのクラスのクライアントの観点から考えてください。それらは整数のリストが期待されるところで整数のセットを受け取るかもしれません。たとえその値がすでにリストに存在していても、クライアントは値を追加してその値をリストに追加したいかもしれません。しかし、値が存在する場合、彼女はそのような動作をしません。彼女にとって大きな驚きです。

これは継承の不適切な使用の典型的な例です。この場合はコンポジションを使用してください。

(からのフラグメント:継承を正しく使う


2

コンポジションv / s継承は幅広い主題です。私はそれがすべてシステムの設計に依存していると思うので、何がより良いのかについて真の答えはありません。

一般的にオブジェクト間の関係のタイプはそれらの一つを選ぶためのより良い情報を提供します。

関係タイプが「IS-A」関係である場合、継承はより良いアプローチです。 それ以外の場合、関係タイプは "HAS-A"関係であり、構成はより適切なアプローチになります。

それは実体的な関係に完全に依存します。


2

新しいプログラマーのために別の観点からこの質問に取り組むには:

オブジェクト指向プログラミングを学ぶとき、継承は早くから教えられることが多いので、それは一般的な問題に対する簡単な解決策と見られています。

私は3つのクラスがあり、それらすべてに共通の機能が必要です。だから私は   基底クラスを書いて、それらすべてから継承するようにすれば、   すべてがその機能を持っていて、私は一度にそれを維持する必要があるでしょう   場所。

それは素晴らしいように思えますが、実際にはいくつかの理由のうちの1つのために、実際には動作しません。

  • クラスに持たせたい他の機能がいくつかあることを私たちは発見しました。クラスに機能を追加する方法が継承によるのであれば、決定する必要があります。それを継承するすべてのクラスがその機能を必要とするわけではありませんが、既存の基本クラスに追加しますか。別の基本クラスを作成しますか?しかし、もう一方の基本クラスからすでに継承しているクラスはどうでしょうか。
  • 私たちは、私たちの基本クラスから継承するクラスのうちの1つのみについて、基本クラスが少し異なる振る舞いをしたいことを発見しました。それでは今、私たちは基本クラスに戻り、仮想メソッドを追加するか、さらに悪いことに、「私はタイプAを継承していればこれを行いますが、タイプBを継承すればそれを行います) "それは多くの理由で悪いです。 1つは、基本クラスを変更するたびに、継承クラスを事実上すべて変更しているということです。クラスAでは少し異なる動作が必要なので、クラスA、B、C、およびDを実際に変更しています。考えているように、これらのクラスの1つを壊す可能性があります。クラス。
  • これらのクラスをすべて互いに継承させることにした理由はわかっているかもしれませんが、私たちのコードを保守しなければならない人にとっては意味がないかもしれません(おそらくそうではないでしょう)。私たちは彼らに難しい選択を強いるかもしれません - 私が必要とする変更をするために本当に醜くて面倒なことをするか(前の箇条書きを見てください)、あるいは単にこの一束を書き直すか。

結局、私たちは自分のコードをいくつかの難しい結び目に結び付けていて、「クール、継承について学んだので今はそれを使いました」と言う以外は何もしません。私たち全員がそれをやったからといって、納得のいくものではありません。しかし、誰も私たちにしないように言わなかったので私たち全員がそれをしました。

誰かが「継承よりも合成を支持する」と説明した直後に、継承を使用してクラス間で機能を共有しようとするたびに考え直し、ほとんどの場合それが実際にうまく機能しないことに気付きました。

解毒剤は単一責任原則。それを制約と考えてください。私のクラスしなければならない一つのことをする。私しなければならない私のクラスに、そのことの1つを説明する名前を付けることができます。 (すべてに例外がありますが、学習しているときは絶対規則の方が優れている場合があります。)という基本クラスを書くことはできません。ObjectBaseThatContainsVariousFunctionsNeededByDifferentClasses。私が必要とするどんな明確な機能もそれ自身のクラスになければならず、そしてその機能を必要とする他のクラスはそのクラスに依存することができますではないそれから継承します。

単純化し過ぎる危険性があるので、それは構成 - 一緒に機能するように複数のクラスを構成することです。そしてその習慣を形成すると、継承を使うよりもはるかに柔軟で、保守しやすく、テストしやすいことがわかります。


  • クラスが複数の基底クラスを使用できないということは、継承に関する反省が不十分であるのではなく、特定の言語の機能の欠如に対する反省が不十分であることを意味します。 - iPherian
  • この答えを書いて以来、私は読みましたこの郵便受け"Uncle Bob"よりこれは、その能力の欠如に対処します。多重継承を許可する言語を使用したことがありません。しかし、振り返ってみると、質問には「言語にとらわれない」というタグが付けられています。そして私の答えはC#を想定しています。視野を広げる必要があります。 - Scott Hannen

1

多くの人が言ったように、私は最初にチェックから始めます - "is-a"関係が存在するかどうか。それが存在する場合、私は通常以下をチェックします:

基本クラスをインスタンス化できるかどうか。つまり、基本クラスが非抽象クラスになることができるかどうかです。それが抽象的ではない場合、私は通常作文を好む

例1.会計士です従業員。でも私はするではないEmployeeオブジェクトはインスタンス化できるため、継承を使用してください。

例2.予約ですSellingItem SellingItemはインスタンス化できません - それは抽象的な概念です。だから私はinheritacneを使います。 SellingItemは抽象基本クラス(またはインタフェースC#で)

このアプローチについてどう思いますか?

また、私は@anon answerをサポートしています。なぜ継承を使うのですか?

継承を使う主な理由はコンポジションの形式としてではありません - それはあなたが多態的な振る舞いを得ることができるようにです。多態性が必要ない場合は、おそらく継承を使用しないでください。

@ MatthieuM。で言うhttps://softwareengineering.stackexchange.com/questions/12439/code-smell-inheritance-abuse/12448#comment303759_12448

継承に関する問題は、それが2つの直交する目的に使用できるということです。

インターフェース(多態性用)

実装(コード再利用のため)

参照

  1. どのクラスデザインが良いですか?
  2. 継承と集計


  • 基本クラスが抽象クラスである理由がよくわからないのですが。議論の中の数字.. LSP:Poodleオブジェクトが渡された場合、Dogs上で動作するすべての機能は動作しますか?そうであれば、プードルは犬の代わりになることができ、それゆえ犬から受け継ぐことができます。 - Gishu
  • と言っています私は間違いなくLSPを調べます。しかしその前に、「継承が適切で、基本クラスが抽象化できない例」を提供してください。私が思うに、継承は基本クラスが抽象的である場合にのみ適用可能です。基本クラスを個別にインスタンス化する必要がある場合は、継承しないでください。つまり、会計士は従業員ですが、継承を使用しないでください。 - Lijo
  • 最近WCFを読んでいます。 .netフレームワークの例は、ThreadPoolスレッド上で作業をキューに入れるSynchronizationContext(base +をインスタンス化することができます)です。派生物には、WinFormsSyncContext(UIスレッドへのキュー)およびDispatcherSyncContext(WPFディスパッチャーへのキュー)が含まれます。 - Gishu
  • と言っていますただし、Bankドメイン、HRドメイン、Retailドメイン、またはその他の一般的なドメインに基づいたシナリオを提供できればさらに便利です。 - Lijo
  • ごめんなさい。私はこれらのドメインに精通していません。前のものがあまりにも曖昧だった場合の別の例はWinforms / WPFのControlクラスです。基本/総称制御をインスタンス化することができます。派生物には、リストボックス、テキストボックスなどが含まれます。それを考えると、デコレータデザインパターンは私見の良い例であり、便利です。デコレータは、ラップ/デコレートしたい非抽象オブジェクトから派生します。 - Gishu

1

作曲が好まれるとしても、私は長所を強調したいと思います。継承との短所組成

継承の長所:

  1. それは論理を確立します「Aです」関係。もしそしてトラック2種類あります車両(基本クラス)、子どもクラスAです基本クラス

    すなわち

    車は車です

    トラックは車です

  2. 継承により、機能を定義/修正/拡張することができます

    1. 基本クラスは実装を提供せず、サブクラスは完全なメソッドをオーバーライドする必要があります(抽象)=>あなたは契約を実装することができます
    2. 基本クラスはデフォルトの実装を提供し、サブクラスは動作を変更できます=>契約を再定義できます
    3. サブクラスは、最初のステートメントとしてsuper.methodName()を呼び出すことによって、基本クラスの実装に拡張を追加します。あなたは契約を延長することができます
    4. 基本クラスはアルゴリズムの構造を定義し、サブクラスはalgorithm =>の一部をオーバーライドしますあなたは実装することができますTemplate_method基本クラスのスケルトンに変更なし

コンポジションの短所

  1. 継承では、サブクラスがベースクラスメソッドを実装していなくても、サブクラスはベースクラスメソッドを直接呼び出すことができます。Aです関係。コンポジションを使用する場合は、コンテナクラスにメソッドを追加して、含まれているクラスAPIを公開する必要があります。

例えばもし含む車両そしてあなたがの価格を取得する必要がある場合で定義されている車両あなたのコードはこのようになります

class Vehicle{
     protected double getPrice(){
          // return price
     }
} 

class Car{
     Vehicle vehicle;
     protected double getPrice(){
          return vehicle.getPrice();
     }
} 


  • 質問には答えないと思います - almanegra
  • あなたはOP質問を再検討することができます。私は演説しました:それぞれのアプローチのためにどんなトレードオフがありますか? - Ravindra babu
  • あなたが言及したように、あなたは「相続の長所と構成の短所」についてだけ話します、そしてEACHアプローチのためのトレードオフまたはあなたがもう一方を使うべきではないケースについては話しません - almanegra
  • 継承の長所は構成の短所であり、構成の短所は継承の長所であるため、長所と短所はトレードオフを提供します。 - Ravindra babu

リンクされた質問


関連する質問

最近の質問