継承よりも構成を優先しますか?


1604

継承よりも構成を好むのはなぜですか?各アプローチにはどのようなトレードオフがありますか?いつ合成より継承を選ぶべきですか?



40
その質問についての良い記事がここにあります。私の個人的な見解では、設計する「より良い」または「悪い」原則はありません。具体的なタスクには「適切」と「不適切」の設計があります。つまり、状況に応じて、継承と構成の両方を使用します。目標は、より小さなコードを生成し、読みやすく、再利用し、最終的にはさらに拡張することです。
m_pGladiator 2008

1
パブリックメソッドがあり、それを変更すると、1つの文の継承がパブリックになり、公開されたAPIが変更されます。構成があり、構成されたオブジェクトが変更された場合、公開されたAPIを変更する必要はありません。
Tomer Ben David、2015

回答:


1188

継承よりも構成が優先されます。これは、より柔軟で、後で簡単に変更できるためです。ただし、常に作成する方法は使用しないでください。構成では、依存性注入/セッターを使用して動作をその場で簡単に変更できます。ほとんどの言語では複数の型からの派生を許可していないため、継承はより厳格です。TypeAから派生したガチョウは多かれ少なかれ調理されます。

上記の私の酸テストは:

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

    • たとえば、セスナ複葉機は飛行機の完全なインターフェースを公開します。そのため、飛行機から派生するのに適しています。
  • TypeBはTypeAによって公開される動作の一部または一部のみを必要としますか?構成の必要性を示します

    • たとえば、鳥は飛行機の飛行動作のみを必要とする場合があります。この場合、それをインターフェース/クラス/両方として抽出し、両方のクラスのメンバーにすることが理にかなっています。

更新:ちょうど私の答えに戻ってきましたが、「このタイプから継承する必要がありますか?」のテストとして、Barbara LiskovのLiskov Substitution Principleの具体的な言及がなければ、不完全なようです。


81
2番目の例は、Head First Design Patterns(amazon.com/First-Design-Patterns-Elisabeth-Freeman/dp/…)の本から抜粋したものです。:)この質問をググる人には、この本を強くお勧めします。
ジェシュルン

4
それは非常に明確ですが、何かを見落とす可能性があります:「TypeBは、TypeAが期待される場所でTypeBを使用できるように、TypeAの完全なインターフェース(すべてのパブリックメソッド)を公開したいですか?」しかし、これが真実で、TypeBがTypeCの完全なインターフェースを公開している場合はどうでしょうか?TypeCがまだモデル化されていない場合はどうなりますか?
トリスタン

4
あなたは私が最も基本的なテストであるべきだと思っていることをほのめかします:「このオブジェクトは、基本型(のであろう)のオブジェクトを期待するコードによって使用可能でしょうか」。答えが「はい」の場合、オブジェクト継承する必要があります。いいえの場合、それはおそらくすべきではありません。もし私がドラチャーを持っているなら、言語は「このクラス」を参照するキーワードを提供し、別のクラスのように振る舞うべきであるがそれに代わるものではないクラスを定義する手段を提供します(そのようなクラスはすべて「このクラスを持つでしょう」クラス」参照自体に置き換えられます)。
スーパーキャット'12

22
@Alexey-重要なのは、「セスナ複葉機を、飛行機を期待するすべてのクライアントに驚くことなく渡すことができるか?」です。はいの場合は、継承が必要な可能性があります。
岐阜2012年

9
私は実際に継承が私の答えであると思われる例を考えるのに苦労しています、私はしばしば集約、構成、およびインターフェースがよりエレガントなソリューションをもたらすことを発見します。上記の例の多くは、これらのアプローチを使用してより適切に説明できる可能性があります...
スチュアートウェイクフィールド

415

封じ込めに関係があると考えてください。車にはエンジンがあり、人には名前があります。

継承関係である考えてください。車は乗り物であり、人は哺乳類である、など。

私はこのアプローチを信用していません。私は、Steve McConnellによるコードコンプリートの第2版セクション6.3からそれを直接取り入れました。


107
これは常に完璧なアプローチとは限りません。単に良いガイドラインです。リスコフ置換の原則ははるかに正確です(失敗は少なくなります)。
ビルK

41
「私の車には車両があ​​ります。」プログラミングの文脈ではなく、それを別の文と見なすと、まったく意味がありません。そして、それがこの手法の要点です。不自然に聞こえる場合は、おそらく間違っています。
Nick Zalutskiy、2011

36
@ニック確かですが、「私の車にはVehicleBehaviorがあります」の方が理にかなっています(「Vehicle」クラスの名前は「VehicleBehavior」のようになると思います)。したがって、「ある」と「ある」の比較に基づいて決定を下すことはできません。LSPを使用する必要があります。そうしないと、間違いが発生します
トリスタン

35
「ある」の代わりに「のように振る舞う」と考えます。継承とは、セマンティクスだけでなく、動作を継承することです。
ybakos 2012年

4
@ybakos「のように振る舞う」は、継承を必要とせずにインターフェースを介して実現できます。ウィキペディアから「継承を介した合成の実装は、通常、システムが示す必要のある動作を表すさまざまなインターフェースの作成から始まります。したがって、システムの動作は継承なしで実現されます。」
DavidRR、2015年

210

違いを理解すれば説明しやすくなります。

手続きコード

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

継承

これにより、クラスの使用が促進されます。継承は、OO設計の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オブジェクトは、従業員と個人として構成されます。タイトルの動作は従業員から取得されます。この明示的な構成により、あいまいさが取り除かれ、バグが少なくなります。


6
継承の場合:あいまいさはありません。要件に基づいてManagerクラスを実装しています。したがって、要件が指定されている場合は「オペレーションマネージャー」を返し、それ以外の場合は基本クラスの実装を使用します。また、Personを抽象クラスにして、下流のクラスがTitleプロパティを実装するようにすることもできます。
Raj Rao

68
「継承よりも構成」と言うかもしれないが、それは「常に継承よりも構成」を意味しないことを覚えておくことが重要です。「ある」は継承を意味し、コードの再利用につながります。従業員は人です(従業員には人がいません)。
Raj Rao

36
例はわかりにくいです。従業員は人なので、継承を使用する必要があります。この例ではコンポジションを使用しないでください。技術的にはコードで宣言できますが、ドメインモデルでは関係が間違っているためです。
Michael Freidgeim 2013

15
私はこの例に同意しません。Employee is-a Personは、継承を適切に使用するための教科書のケースです。Titleフィールドの再定義の「問題」は意味がないと私は思います。Employee.TitleがPerson.Titleをシャドウしているという事実は、プログラミングが不十分であることを示しています。結局、「さん」です。と「運用のマネージャー」は本当に人(小文字)の同じ側面を指しますか?私はEmployee.Titleの名前を変更するので、従業員のTitle属性とJobTitle属性を参照できます。これらはどちらも実際には意味があります。さらに、マネージャーの理由はありません(続き...)
Radon Rosborough 2014年

9
(...続き)PersonとEmployeeの両方から継承する-結局のところ、EmployeeはすでにPersonから継承しています。人がマネージャとエージェントになる可能性があるより複雑なモデルでは、多重継承を使用できることは事実です(慎重に!)が、多くの環境では、マネージャ(従業員を含む)からの抽象Roleクラスが望ましい(彼/彼女が管理する)およびエージェント(契約およびその他の情報を含む)は継承します。次に、従業員は複数の役割を持つ人です。したがって、構成と継承の両方が適切に使用されます。
Radon Rosborough、2014年

142

継承によって提供される紛れもない利点のすべてに、そのいくつかの欠点があります。

継承の短所:

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

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


5
これは私の意見ではより良い答えの1つです-私はこれに加えて、構成の観点から問題を再考しようとすると、私の経験では、より小さく、より単純で、より自己完結型で、より再利用可能なクラスにつながる傾向があります、より明確で、より小さく、より焦点を絞った責任範囲。多くの場合、これは依存関係の注入や(テストでの)モックなどの必要性が少ないことを意味します。これは、通常、より小さなコンポーネントが独自に立つことができるためです。ただ私の経験。YMMV :-)
mindplay.dk 2014年

3
この投稿の最後の段落は本当に私にクリックされました。ありがとうございました。
Salx

87

継承よりも構成を優先するもう1つの非常に実用的な理由は、ドメインモデルを使用して、それをリレーショナルデータベースにマッピングすることです。継承をSQLモデルにマッピングするのは非常に困難です(常に使用されるとは限らない列の作成、ビューの使用など、あらゆる種類のハックな回避策が発生します)。一部のORMLはこれに対処しようとしますが、常にすぐに複雑になります。2つのテーブル間の外部キーリレーションシップを使用して構成を簡単にモデル化できますが、継承ははるかに困難です。


81

簡単に言えば、「継承よりも構成を優先する」に同意しますが、「コカコーラよりもジャガイモを優先する」ように聞こえることがよくあります。継承する場所と構成する場所があります。違いを理解する必要があります。そうすれば、この質問は消えます。私にとってそれが本当に意味することは、「継承を使用する場合、もう一度考えてみてください。おそらく、構成が必要なのです」です。

食べたいときはコカコーラよりもジャガイモを好み、飲みたいときはジャガイモよりコカコーラを好むべきです。

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

だから私にとっては、相続や作文が必要かどうかわからないように見えますが、本当の問題は、飲みたいのか食べたいのかわからないことです。問題のある領域についてもっと考え、よく理解してください。


5
適切な仕事に最適なツール。ハンマーはレンチよりも物を叩くのに優れているかもしれませんが、それは、レンチを「劣ったハンマー」と見なすべきだという意味ではありません。継承は、オブジェクトがスーパークラスオブジェクトとして動作するためにサブクラスに追加されるものが必要な場合に役立ちます。たとえばInternalCombustionEngine、派生クラスを持つ基本クラスを考えGasolineEngineます。後者は、基本クラスにはないスパークプラグなどを追加しますInternalCombustionEngineが、スパークプラグを使用すると、スパークプラグが使用されるようになります。
スーパーキャット2012年

61

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

継承はおそらくあなたが持つことができる最悪の形の結合です

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

記事「継承は悪である:DataAnnotationsModelBinderの重大な失敗」では、C#でのこの例を紹介しています。これは、コンポジションが使用されるべきであった場合の継承の使用法と、リファクタリングの方法を示しています。


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

42

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

したがって、オブジェクトが別のオブジェクトとして表示される必要がある場合、またはオブジェクトの状態や条件に応じて異なる動作をする必要がある場合は、コンポジションを使用します。状態戦略の設計パターンを参照してください。

オブジェクトを同じ型にする必要がある場合は、継承を使用するか、インターフェイスを実装します。


10
+1継承がほとんどの状況で機能することは、ますます少なくなっています。私は共有/継承されたインターフェースとオブジェクトの構成を好みます...それとも集約と呼ばれますか?私に質問しないでください、私はEEの学位を持っています!!
ケニー

どちらも理論的には当てはまる可能性があるため、これは「継承よりも構成」が適用される最も一般的なシナリオだと思います。たとえば、マーケティングシステムでは、の概念がある場合がありますClient。その後、新しいコンセプトのPreferredClientポップアップが後に登場します。PreferredClient継承する必要がありClientますか?結局のところ、優先クライアントは「クライアント」です。まあ、それほど速くない...あなたが言ったようにオブジェクトは実行時にクラスを変えることができない。どのようにclient.makePreferred()操作をモデル化しますか?おそらくその答えは、Accountおそらく欠落しているコンセプトのあるコンポジションを使用することでしょうか?
plalx

むしろ、異なるタイプの持つよりもClientクラスを、おそらくの概念をカプセル化1だけがありますAccountすることができたStandardAccountPreferredAccount...
plalx

40

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

「継承よりも構成を優先する」理由を理解するには、最初に、この短縮されたイディオムで省略された仮定を取り戻す必要があります。

継承には2つの利点があります。サブタイピングとサブクラス化です。

  1. サブタイピングとは、タイプ(インターフェース)シグニチャー、つまりAPIのセットに準拠することを意味し、サブタイプのポリモーフィズムを実現するためにシグニチャーの一部をオーバーライドできます。

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

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

コードの再利用が唯一の目的である場合、サブクラス化は必要なものより1つ多く提供する可能性があります。つまり、親クラスのいくつかのパブリックメソッドは子クラスにはあまり意味がありません。この場合、継承よりも構成を優先する代わりに、構成が要求されます。これは「is-a」対「has-a」の概念の由来でもあります。

したがって、サブタイピングが目的の場合、つまり後で新しいクラスを多態的に使用する場合にのみ、継承または構成を選択するという問題に直面します。これは、議論中の短縮されたイディオムで省略される前提です。

サブタイプとは、タイプシグネチャに準拠することです。これは、コンポジションが常に同じタイプのAPIを公開する必要があることを意味します。ここでトレードオフが始まります:

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

  2. 継承は単純明快提供オープン再帰を内部多型部位を介してthis、すなわちオーバーライドメソッド(あるいは呼び出すタイプ別のメンバ関数では)、のいずれか(ただし、パブリックまたはプライベート落胆)。オープン再帰は、compositionを介してシミュレートできますが、追加の作業が必要であり、常に実行できるとは限りません(?)。重複した質問に対するこの回答は、同様のことを語っています。

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

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

  5. 合成には、コンビネータ指向のプログラミングの利点があります。つまり、複合パターンのように動作します

  6. 構成は、インターフェイスへのプログラミングの直後に行われます

  7. 構成には、多重継承が容易であるという利点があります。

上記のトレードオフを考慮して、継承よりも構成を優先します。しかし、密接に関連するクラス、つまり暗黙的なコードの再利用が本当にメリットをもたらす場合、またはオープンな再帰の魔法の力が望まれる場合、継承が選択されます。


34

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

構成は多くの場合、より論理的であり、抽象化、カプセル化、コードの再利用が向上し(特に非常に大規模なプロジェクトで)、コード内のどこかで孤立した変更を行ったという理由だけで、離れたところにあるものを壊す可能性が低くなります。また、「単一の責任の原則」を支持することが容易になります。これは、「クラスが変更される理由は1つ以上あるべきではありません」とまとめられることが多く、すべてのクラスが特定の目的のために存在し、その目的に直接関連するメソッドのみを持っています。また、継承ツリーが非常に浅いため、プロジェクトが非常に大きくなり始めた場合でも、概要を簡単に維持できます。多くの人々は、継承は私たちの現実の世界を表すと考えています結構ですが、それは真実ではありません。現実の世界では、継承よりもはるかに多くの構成を使用しています。手に持つことができるほとんどすべての実世界のオブジェクトは、他のより小さな実世界のオブジェクトで構成されています。

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

ほとんどのオブジェクト指向言語は多重継承を許可していないことに気づいたかもしれません。多重継承が実際にあなたに何かを買うことができるいくつかのケースがありますが、それらは規則よりもむしろ例外です。「複数の継承がこの問題を解決するための本当に素晴らしい機能になるだろう」と思われる状況に遭遇したときはいつでも、通常、継承を完全に再考する必要があります。 、構成に基づくソリューションは、通常、はるかにエレガントで、柔軟性があり、将来を見据えたものになります。

継承は本当に素晴らしい機能ですが、ここ2、3年は使いすぎていると思います。人々は継承を、それが実際に釘であるか、ねじであるか、あるいは何か完全に異なるものであるかに関係なく、すべてを釘付けできる1つのハンマーとして扱いました。


「多くの人々は、継承は私たちの現実の世界をかなりよく表していると考えていますが、それは真実ではありません。」これくらい!これまでの世界のほとんどすべてのプログラミングチュートリアルとは対照的に、現実世界のオブジェクトを継承チェーンとしてモデル化することは、長期的には悪い考えです。継承は、信じられないほど明白で生得的な単純なis-a関係がある場合にのみ使用してください。のようなものTextFileですFile
neonblitzer

25

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

理由:サブクラス化は通常、より複雑で関連性があることを意味します。つまり、間違いを犯さずに変更、維持、拡張することが困難になります。

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

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

  • 無邪気な行為は予期しない結果をもたらす可能性があります -この典型的な例は、サブクラスのインスタンスフィールドが初期化される前の、スーパークラスコンストラクターからのオーバーライド可能なメソッドの呼び出しです。完璧な世界では、誰もそれをすることはありません。これは完璧な世界ではありません。
  • これは、サブクラス作成者がメソッド呼び出しの順序などについて仮定を立てるというひねくれた誘惑を提供します。スーパークラスが時間とともに進化する可能性がある場合、そのような仮定は安定しない傾向があります。トースターとコーヒーポットの例えもご覧ください。
  • クラスは重くなります。スーパークラスがコンストラクターで実行している作業や、使用するメモリの量が必ずしもわかるとは限りません。したがって、無害な軽量オブジェクトを構築することは、あなたが考えるよりもはるかに高価になる可能性があり、スーパークラスが進化する場合、これは時間とともに変化する可能性があります
  • サブクラスの爆発的な増加を助長します。クラスローディングには時間がかかり、クラスが増えるとメモリが消費されます。NetBeansの規模でアプリを扱うまでは問題にならないかもしれませんが、メニューの最初の表示が大量のクラスの読み込みをトリガーしたためにメニューが遅くなるなど、実際の問題がありました。より宣言的な構文やその他の手法に移行することでこれを修正しましたが、修正にも時間がかかりました。
  • 後で変更するのが難しくなります。クラスをパブリックにした場合、スーパークラスをスワップするとサブクラスが壊れます。コードをパブリックにすると、結婚することになります。したがって、実際の機能をスーパークラスに変更しない場合は、必要なものを拡張するよりも、後で使用するほうがはるかに自由に変更できます。たとえば、JPanelをサブクラス化するとします。これは通常間違っています。サブクラスがどこかで公開されている場合、その決定を再検討する機会はありません。JComponent getThePanel()としてアクセスされた場合でも、それを行うことができます(ヒント:内のコンポーネントのモデルをAPIとして公開します)。
  • オブジェクト階層はスケーリングしません(または、後で階層化することは、事前に計画するよりもはるかに困難です)。これは、古典的な「層が多すぎる」問題です。以下でこれについて説明し、AskTheOracleパターンがそれをどのように解決できるかを示します(ただし、OOPの純粋主義者を怒らせる可能性があります)。

...

あなたが継承を許可する場合、何をすべきかについての私の見解は、塩の粒で取ることができます:

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

...

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


25

継承よりも構成を好むのはなぜですか?

他の回答を参照してください。

継承はいつ使用できますか?

次の文が当てはまる場合、Barクラスはクラスを継承できるとよく言われFooます。

  1. バーはフーです

残念ながら、上記のテストだけでは信頼できません。代わりに以下を使用してください。

  1. バーはFOOある、
  2. バーはfoosができることはすべてできる。

すべてのその最初のテスト性を保証ゲッターFoo中メイクセンスBar(=共有特性)、第二の試験は確かにすべてのことをする間セッターFoo中メイクセンスBar(=共有機能)。

例1:犬->動物

犬は動物であり、犬は動物ができるすべてのことを行うことができます(呼吸、死亡など)。したがって、クラスはクラスDog 継承できAnimalます。

例2:円-/->楕円

円は楕円ですが、円は楕円ができることすべてを行うことはできません。たとえば、円は伸縮できませんが、楕円は伸縮できます。したがって、クラスはクラスCircle 継承できませんEllipse

これはCircle-Ellipse問題と呼ばれ、実際には問題ではありません。最初のテストだけでは継承が可能であると結論付けるのに十分ではないことの明確な証拠です。特に、この例は、派生クラスが基本クラスの機能を拡張し制限しないことを強調しています。それ以外の場合、基本クラスは多態的に使用できませんでした。

継承はいつ使用すべきですか?

あなたがいてもすることができます継承を使用し、あなたが意味するものではありませんべき:組成物を用いて、常にオプションです。継承は、暗黙的なコードの再利用と動的なディスパッチを可能にする強力なツールですが、いくつかの欠点があるため、多くの場合、構成が優先されます。継承と構成の間のトレードオフは明らかではありません。私の意見では、lcnの回答で最もよく説明されています

経験則として、私は、ポリモーフィックな使用が非常に一般的であると予想される場合は、コンポジションより継承を選択する傾向があります。たとえばWidget、GUIフレームワークにポリモーフィッククラスを、またはNodeXMLライブラリにポリモーフィッククラスを使用すると、純粋に構成に基づいたソリューションよりもはるかに読みやすく、直感的に使用できるAPIを使用できます。

リスコフ代替原則

ご存知のように、継承が可能かどうかを判断するために使用される別の方法は、リスコフ代入原則と呼ばれます。

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

基本的に、これは、基本クラスを多態的に使用できる場合に継承が可能であることを意味します。これは、「バーはfooであり、バーはfooが実行できるすべてのことを実行できる」というテストに相当すると私は考えています。


円楕円と四角形のシナリオは、良い例ではありません。サブクラスは常にスーパークラスよりも複雑であるため、問題が発生します。この問題は、関係を逆にすることで解決されます。楕円は円から、長方形は正方形から派生します。これらのシナリオでコンポジションを使用するのは非常にばかげています。
ファジーロジック

@FuzzyLogicは同意しましたが、実際、私の投稿では、この場合にコンポジションを使用することを推奨していません。Circle-Ellipseの問題は、 "is-a"が単独でCircleがEllipseから派生したものであると結論付けるための良いテストではない理由の良い例であるとだけ述べました。実際に結論すると、LSPの違反のため、CircleはEllipseから派生するべきではありません。可能なオプションは、関係を反転するか、構成を使用するか、テンプレートクラスを使用するか、追加のクラスまたはヘルパー関数を含むより複雑な設計を使用することです。など...そして決定は明らかにケースバイケースで行われるべきです。
Boris Dalstein、

1
@FuzzyLogicまた、Circle-Ellipseの特定のケースについて私が主張することについて興味がある場合は、Circleクラスを実装しないことを主張します。関係を逆にすることの問題は、LSPにも違反することです:関数を想像してくださいcomputeArea(Circle* c) { return pi * square(c->radius()); }。Ellipseを渡すと明らかに壊れます(radius()はどういう意味ですか?)。楕円は円ではないため、円から派生してはなりません。
Boris Dalstein、

computeArea(Circle *c) { return pi * width * height / 4.0; }今では一般的です。
ファジィロジック

2
@FuzzyLogic私は反対する:あなたは、クラスサークルが派生クラス楕円の存在を予想し、そのために提供することを、この手段を実現するwidth()height()?ライブラリユーザーが「EggShape」と呼ばれる別のクラスを作成することにした場合はどうなりますか?「Circle」から派生する必要がありますか?もちろん違います。卵の形は円ではなく、楕円も円ではないため、LSPを壊すため、Circleから派生するものはありません。Circle *クラスで操作を実行するメソッドは、円が何であるかについて強力な仮定を行います。これらの仮定を破ると、ほぼ確実にバグが発生します。
Boris Dalstein、


15

継承は、サブクラスとスーパークラスの間に強い関係を作成します。サブクラスは、スーパークラスの実装の詳細を認識する必要があります。スーパークラスを拡張する方法を考える必要がある場合、スーパークラスの作成ははるかに困難です。クラスの不変条件を慎重に文書化し、オーバーライド可能なメソッドが内部で使用する他のメソッドを指定する必要があります。

階層が本当に関係を表す場合、継承が役立つことがあります。それはクラスが変更のために閉じられるべきであるが拡張に対して開かれるべきであると述べているOpen-Closed Principleに関連しています。そうすれば、ポリモーフィズムを持つことができます。スーパータイプとそのメソッドを処理するジェネリックメソッドを持ちますが、動的ディスパッチを介してサブクラスのメソッドが呼び出されます。これは柔軟性があり、間接的なものを作成するのに役立ちます。これは、ソフトウェアに不可欠です(実装の詳細についてあまり知らないため)。

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

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


3
継承がポリモーフィズムを実現する唯一の方法ではないことは注目に値します。デコレータパターンは、構成を通じて多態性の外観を提供します。
BitMask777

1
@ BitMask777:サブタイプのポリモーフィズムは1種類のポリモーフィズムのみで、もう1つはパラメトリックポリモーフィズムです。そのための継承は必要ありません。さらに重要なこと:継承について話すとき、1つはクラスの継承を意味します。つまり、複数のクラスに共通のインターフェースを持つことにより、サブタイプのポリモーフィズムを持つことができ、継承の問題は発生しません。
egaga 2012

2
@engaga:私はあなたのコメントInheritance is sometimes useful... That way you can have polymorphismを、継承とポリモーフィズムの概念をハードリンクするものとして解釈しました(サブタイピングはコンテキストを前提として想定されています)。私のコメントは、あなたがコメントで明確にしていることを指摘することを意図していました。その継承は、ポリモーフィズムを実装する唯一の方法ではなく、実際には、構成と継承を決定する際の決定要因ではありません。
BitMask777 2012年

15

航空機にエンジンと翼の2つの部分しかないと仮定します。
次に、航空機クラスを設計するには2つの方法があります。

Class Aircraft extends Engine{
  var wings;
}

これで、航空機は翼を固定した状態で開始し
、その場で回転翼に変更できます。それは本質的に
翼を持つエンジンです。しかし
、その場でエンジンを変更したい場合はどうなりますか?

基本クラスEngineは、その
プロパティを変更するためのミューテーターを公開するか、次のように再設計Aircraftします。

Class Aircraft {
  var wings;
  var engine;
}

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


あなたの投稿は私が前に考慮しなかったポイントを持ち出します-銃器のようなもので、複数のパーツを持つ機械的なオブジェクトの類推を続けるために、シリアル番号が付いていると考えられているシリアル番号でマークされた1つのパーツがあります銃器全体のサイズ(拳銃の場合、通常はフレームになります)。他のすべての部品を交換しても同じ銃器を使用できますが、フレームが割れて交換する必要がある場合は、元の銃の他のすべての部品で新しいフレームを組み立てると、新しい銃になります。注
スーパーキャット2012

...銃の複数の部品にシリアル番号が付いている場合があるという事実は、銃が複数のIDを持つことができることを意味するものではありません。フレームのシリアル番号のみが銃を識別します。他の部品のシリアル番号は、それらの部品が組み立てられるように製造された銃を識別します。これは、いつでも組み立てられる銃ではない可能性があります。
スーパーキャット2012


7

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

この1つの例:リストからスタックを作成するとします。スタックにはポップ、プッシュ、ピークのみがあります。Stackにpush_back、push_front、removeAtなどの機能が必要ない場合は、継承を使用しないでください。


7

これらの2つの方法は、一緒にうまく生活し、実際にお互いをサポートすることができます。

構成は、それをモジュラーで再生するだけです。親クラスと同様のインターフェースを作成し、新しいオブジェクトを作成して、その呼び出しを委任します。これらのオブジェクトがお互いを知る必要がない場合は、コンポジションを使用するのは非常に安全で簡単です。ここには非常に多くの可能性があります。

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

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

どうして?

継承は情報を移動するための貧弱な方法だからです。

構成には本当の意味があります。関係は逆にすることができます。「親クラス」または「抽象ワーカー」は、特定のインターフェースを実装する特定の「子」オブジェクトを集約できます。タイプです。オブジェクトはいくつでも存在できます。たとえば、MergeSortやQuickSortは、抽象的なCompareインターフェースを実装するオブジェクトのリストを並べ替えることができます。または別の言い方をすると、「foo()」を実装するオブジェクトのグループと、「foo()」を持つオブジェクトを利用できる他のオブジェクトのグループは、一緒に遊ぶことができます。

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

  1. 同じインターフェースを持つ多くのクラスがあり、それらを書く時間を節約したい
  2. 各オブジェクトに同じ基本クラスを使用する必要があります
  3. プライベート変数を変更する必要があります。これは、いかなる場合でもパブリックにすることはできません。

これらが真の場合、おそらく継承を使用する必要があります。

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

理由が2番の場合、少し注意が必要です。本当に同じ基本クラスを使用する必要があるだけですか?一般に、同じ基本クラスを使用するだけでは十分ではありませんが、フレームワークの要件である可能性があり、これは回避できない設計上の考慮事項です。

ただし、プライベート変数(ケース3)を使用する場合は、問題が発生する可能性があります。グローバル変数を安全でないと考える場合は、継承を使用してプライベート変数へのアクセスも安全でないことを考慮する必要があります。大事なことは、グローバル変数がすべて悪いということではありません。データベースは本質的に大きなグローバル変数のセットです。しかし、それを処理できる場合は、それで問題ありません。


7

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

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

私には3つのクラスがあり、すべてにいくつかの共通機能が必要です。したがって、基本クラスを作成して、それらすべてを継承する場合、それらはすべてその機能を持ち、1か所で維持するだけで済みます。

それは素晴らしいように聞こえますが、実際には、いくつかの理由の1つのために、ほとんど機能しません。

  • クラスに必要な関数が他にもいくつかあることがわかります。クラスに機能を追加する方法が継承によるものである場合は、決定する必要があります。継承するすべてのクラスがその機能を必要とするわけではありませんが、既存の基本クラスに追加するのですか?別の基本クラスを作成しますか?しかし、他の基本クラスからすでに継承しているクラスはどうでしょうか?
  • 基本クラスから継承するクラスの1つだけで、基本クラスの動作が少し異なることを発見しました。さて、戻って基本クラスをいじって、仮想メソッドを追加したり、さらに悪いことに、「タイプAを継承している場合はこれを行いますが、タイプBを継承している場合はそれを行います」 」多くの理由でそれは悪いことです。1つは、基本クラスを変更するたびに、継承されたすべてのクラスを効果的に変更していることです。クラスAで少し異なる動作が必要なため、クラスA、B、C、Dを実際に変更しています。クラス。
  • これらのクラスをすべて相互に継承させることにした理由はわかっているかもしれませんが、コードを保守する必要のある他の人には意味がないかもしれません(おそらく意味をなさないでしょう)。私たちはそれらを難しい選択に追い込むかもしれません-私が必要な変更を行うために本当に醜くて厄介なことをするのでしょうか(前の箇条書きを参照)、あるいは単にこれをたくさん書き直しますか?

結局のところ、コードをいくつかの難しい結び目で結び、「クールです。継承について学び、今はそれを使用しました」と言う以外は、コードから何の利益も得ません。私たち全員がそれを行ったので、それは非難することを意味しません。しかし、誰も私たちにしないように言わなかったので、私たちは皆それをやった。

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

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

単純化しすぎるリスクがありますが、それは構成です。複数のクラスを組み合わせて連携させます。そして、その習慣を形成すると、継承を使用するよりもはるかに柔軟で、保守可能で、テスト可能であることがわかります。


クラスが複数の基本クラスを使用できないということは、継承についての不十分な反映ではなく、特定の言語の機能の欠如についての不十分な反映です。
iPherian 2017

この回答を書いてからしばらくして、私は「Uncle Bob」からこの投稿を読みました。多重継承を可能にする言語を使用したことがありません。しかし振り返ってみると、質問には「言語にとらわれない」というタグが付けられており、私の答えはC#を想定しています。視野を広げる必要があります。
スコットハネン2017

6

とは別に、考慮事項があります。オブジェクトが通過しなければならない継承の「深さ」も考慮する必要があります。継承の深さが5または6レベルを超えると、予期しないキャストおよびボックス化/ボックス化解除の問題が発生する可能性があります。その場合は、代わりにオブジェクトを作成するのが賢明です。


6

2つのクラスの間にis-a関係がある場合(例:dogは犬)、継承に進みます。

一方、あなたが持っている場合がある、またはいくつかの2クラス間の形容詞の関係(学生がコースを持っている)、または(教員研究コース)、あなたは、組成物を選びました。


あなたは相続と相続を言いました。継承と構成を意味するのではないですか?
trevorKirkby 2013

いいえ、必要ありません。犬のインターフェースを定義して、すべての犬にそれを実装させれば、より多くのSOLIDコードが作成されます。
markus

5

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

新しいクラスが同じインターフェースを持つ必要がない場合、つまり、そのクラスのユーザーが知る必要のないクラスの実装の特定の側面を隠したい場合は、コンポジションを使用する必要があります。組成物は、より多くの支援の方法であるので、カプセル化、継承をサポートすることを意味している間(すなわち、隠蔽実装)抽象化(すなわち、この場合には、何かの簡略化された表現を提供同じ異なる内部構造を有するタイプの範囲に対するインターフェイス)。


インターフェースについて言及する場合は+1。このアプローチをよく使用して、既存のクラスを非表示にし、作成に使用するオブジェクトをモックアウトすることにより、新しいクラスを適切にユニットテスト可能にします。これには、代わりに新しいオブジェクトの所有者に候補の親クラスを渡す必要があります。
Kell


4

@Pavelに同意します。彼が言ったとき、作曲の場所と継承の場所があります。

あなたの答えがこれらの質問のいずれかに対して肯定的である場合は、継承を使用する必要があると思います。

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

ただし、意図が純粋にコードの再利用である場合は、構成のほうがデザインを選択する可能性が高くなります。


4

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

サブクラスはサブタイプと同じではありません。サブタイプではないサブクラスを作成する場合があります(これは、コンポジションを使用する必要がある場合です)。サブタイプとは何かを理解するために、タイプとは何かを説明します。

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

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

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

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)メソッドの動作は明らかに変更され、親タイプのプロパティは保持されません。クラスのクライアントの視点から考えます。それらは整数のリストが期待されるところに整数のセットを受け取るかもしれません。クライアントは値を追加し、その値が既にリストに存在する場合でも、その値をリストに追加したい場合があります。しかし、値が存在する場合、彼女はその動作を取得しません。彼女にとって大きな驚き!

これは、継承の不適切な使用の典型的な例です。この場合は構成を使用します。

(からのフラグメント:継承を適切に使用してください)。


3

私が聞いた経験則では、「is-a」関係の場合は継承を使用し、「has-a」の場合は構成を使用する必要があります。それでも、多くの複雑さを排除するので、常に作曲に傾倒すべきだと感じます。


2

構成v / s継承は幅広いテーマです。すべてがシステムの設計に依存すると私が思うので、何がより良いかについての本当の答えはありません。

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

関係タイプが「IS-A」関係の場合、継承の方が適しています。それ以外の場合、リレーションタイプは「HAS-A」リレーションであり、コンポジションがより適切になります。

それは完全にエンティティの関係に依存しています。


2

組成物が好まれていますが、私はプロの強調表示したいの継承との短所構成を

継承の長所:

  1. 論理的な「IS A」関係を確立します。場合はトラックがの2種類があり車両(基底クラス)、子クラスは、ISの基本クラス。

    すなわち

    車は乗り物

    トラックは乗り物

  2. 継承を使用すると、機能を定義/変更/拡張できます

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

構成の短所:

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

たとえば、 CarVehicleが含まれていて、Vehicleで定義されているCarの価格を取得する必要がある場合、コードは次のようになります。

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

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

質問の答えにはならないと思います
アルマネグラ2017年

OPの質問を再確認できます。私は対処しました:各アプローチにはどのようなトレードオフがありますか?
Ravindra babu 2017年

あなたが言ったように、あなたは「継承の長所と構成の短所」についてのみ話し、EACHアプローチのトレードオフや、互いに重ねて使うべきケースについては話しません
アルマネグラ

継承の長所は構成の短所であり、構成の短所は継承の利点であるため、長所と短所はトレードオフを提供します。
Ravindra babu 2017年

1

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

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

例1.会計士従業員です。ただし、Employeeオブジェクトはインスタンス化できるため、継承使用しません

例2. Book は、 SellingItemです。SellingItemはインスタンス化できません-それは抽象的な概念です。したがって、私はinheritacneを使用します。SellingItemは、抽象基本クラス(または C#のインターフェース)です。

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

また、なぜ継承を使用するのですか?

継承を使用する主な理由は、構成の形式としてではありません-ポリモーフィックな動作を得ることができるようにするためです。ポリモーフィズムが必要ない場合は、継承を使用しないでください。

@MatthieuM。/software/12439/code-smell-inheritance-abuse/12448#comment303759_12448で言う

継承の問題は、2つの直交する目的に使用できることです。

インターフェース(ポリモーフィズム用)

実装(コードの再利用)

参照

  1. どのクラスの設計が優れていますか?
  2. 継承と集計

1
「基本クラスが抽象である」理由がわかりません。LSP:Poodleオブジェクトが渡された場合、犬を操作するすべての関数が機能しますか?はいの場合、プードルは犬の代わりになり、犬から継承できます。
岐阜2012

@ギシュありがとう。私は間違いなくLSPを調べます。その前に、「基本クラスが抽象化できない継承が適切な例」を提供していただけませんか。私が思うに、継承は基本クラスが抽象である場合にのみ適用できます。基本クラスを個別にインスタンス化する必要がある場合は、継承しないでください。つまり、会計士は従業員ですが、継承を使用しないでください。
LCJ、

1
最近WCFを読んでいます。.netフレームワークの例は、キューがThreadPoolスレッドに作用するSynchronizationContext(ベース+をインスタンス化できる)です。派生には、WinFormsSyncContext(UIスレッドへのキュー)およびDispatcherSyncContext(WPFディスパッチャーへのキュー)が含まれます
Gishu

@ギシュありがとう。ただし、銀行ドメイン、HRドメイン、小売ドメイン、またはその他の一般的なドメインに基づいたシナリオを提供できれば、さらに役立ちます。
LCJ、2012

1
ごめんなさい。私はそれらのドメインに精通していません。前のドメインがあまりにも鈍かった場合の別の例は、Winforms / WPFのControlクラスです。基本/汎用コントロールはインスタンス化できます。派生には、リストボックス、テキストボックスなどが含まれます。今考えてみると、DecoratorデザインパターンはIMHOの良い例であり、有用でもあります。デコレータは、ラップまたは装飾したい非抽象オブジェクトから派生します。
岐阜2012

1

継承で発生する可能性のあるダイヤモンドの問題について誰も言及していません。

一見すると、クラスBとCがAを継承し、両方がメソッドXをオーバーライドし、4番目のクラスDがBとCの両方を継承し、Xをオーバーライドしない場合、どのXDの実装が使用されることになっていますか?

ウィキペディアは、この質問で議論されているトピックの素晴らしい概要を提供しています。


1
D継承B及びCはないA.そうである場合、それはクラスAであり、Xの実装を使用したい
FABRICIO

1
@fabricio:ありがとう、テキストを編集しました。ちなみに、このようなシナリオは、複数のクラスを継承できない言語では発生しません。
Veverke

はい、あなたは正しいです...と私は(ダイヤモンド問題の例に従って)多重継承を可能にする1 ..で働いたことがありません
FABRICIO
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.