なぜ継承よりも合成を好むのか各アプローチにはどのような妥協点がありますか?あなたはいつコンポジションよりも継承を選ぶべきですか?
継承性よりも合成を優先します。後で柔軟にすることができ、後で変更するのが簡単ですが、常に構成するアプローチを使用しないでください。コンポジションを使用すると、Dependency Injection / Settersを使用してその場で動作を簡単に変更できます。ほとんどの言語では複数の型から派生させることができないため、継承はより厳密です。そのため、TypeAから派生したガチョウは、多かれ少なかれ調理されています。
上記の私の酸テストは:
例えばCessna複葉機は、それ以上でなければ、航空機の完全なインターフェースを公開します。だからそれは飛行機から派生するのに適したものになります。
例えば鳥は飛行機の飛行行動のみを必要とするかもしれません。この場合、それをinterface / class / bothとして抽出し、それを両方のクラスのメンバーにすることは意味があります。
更新:ちょうど私の答えに戻ってきた、それは今バーバラリスコフの具体的な言及なしに不完全であるように思われるリスコフ代用原則「このタイプから継承する必要がありますか」のテストとして
封じ込めをがあります関係。車は「エンジン」、人は「名前」などを持っています。
継承をです関係。車は「車」、人は「哺乳類」などです。
私はこのアプローチには賛成ではありません。私はまっすぐからそれを取った第2版のコード補完によってスティーブマコネル、第6.3節。
違いを理解していれば、説明する方が簡単です。
この例は、クラスを使用していない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として構成されています。役職の動作は従業員から取得されます。この明示的な構成は、とりわけあいまいさを排除し、あなたはより少ないバグに遭遇するでしょう。
継承によって提供される否定できない利点はすべて、ここにいくつかの欠点があります。
継承のデメリット:
一方オブジェクト構成実行時に、他のオブジェクトへの参照を取得するオブジェクトを通じて定義されます。そのような場合、これらのオブジェクトは互いの保護されたデータに到達することができず(カプセル化の中断がない)、互いのインターフェースを尊重することを余儀なくされます。そしてこの場合も、実装の依存関係は継承の場合よりもずっと少なくなります。
継承よりも合成を好むもう1つの非常に実用的な理由は、ドメインモデルとそれをリレーショナルデータベースにマッピングすることです。継承をSQLモデルにマッピングするのは本当に難しいです(あなたは結局いつも使用されるわけではないカラムを作成すること、ビューを使用することなどのようなあらゆる種類の厄介な回避策になります)。 ORMLの中にはこれを処理しようとするものがありますが、それは常に急速に複雑になります。コンポジションは2つのテーブル間の外部キー関係によって簡単にモデル化できますが、継承ははるかに困難です。
一言で言えば、私は「継承よりもコンポジションを好む」に同意するでしょうが、私にとっては「コカコーラよりもポテトを好む」のように聞こえます。継承のための場所と構成のための場所があります。あなたは違いを理解する必要があります、そしてこの質問は消えます。それが私にとって本当に意味することは「あなたが継承を使うつもりならば - もう一度考えてください、あなたは構成が必要であるということです」。
あなたが食べたいときにはコカコーラよりポテトを好み、あなたが飲みたいときにはポテトよりコカコーラを選ぶべきです。
サブクラスを作成することは、スーパークラスのメソッドを呼び出すための単なる便利な方法以上の意味を持つはずです。構造的にも機能的にもサブクラスの「スーパークラス」である場合は継承を使用します。スーパークラスとして使用できる場合はそれを使用します。そうでない場合 - それは継承ではなく、他の何かです。コンポジションとは、あなたのオブジェクトが他のオブジェクトで構成されているか、またはそれらと何らかの関係がある場合です。
だから私にとっては、誰かが自分が相続や構成を必要としているかどうかわからないのであれば、本当の問題は彼が彼が飲みたいのか食べたいのかわからないということです。あなたの問題領域についてもっと考えて、もっとよく理解してください。
InternalCombustionEngine
派生クラスを使ってGasolineEngine
。後者は、基本クラスにはないスパークプラグのようなものを追加しますが、それをInternalCombustionEngine
スパークプラグが使用される原因となります。 - supercat
継承は、特に手続き型の土地から来るとかなり魅力的であり、それはしばしば一見エレガントに見えます。私がしなければならないのは、この1ビットの機能を他のクラスに追加することだけです。まあ、問題の一つはそれです
基本クラスは、実装の詳細を保護されたメンバーの形式でサブクラスに公開することによってカプセル化を破ります。これはあなたのシステムを堅くそして脆弱にします。しかしながら、より悲劇的な欠陥は、新しいサブクラスがそれにすべての手荷物と継承チェーンの意見をもたらすということです。
記事、継承は悪です:DataAnnotationsModelBinderの壮大な失敗、C#でこの例を介して歩きます。それは、コンポジションが使用されるべきであるときの継承の使用と、それがどのようにリファクタリングされることができるかを示します。
JavaまたはC#では、インスタンス化されたオブジェクトはその型を変更できません。
そのため、オブジェクトが別のオブジェクトとして表示される必要がある場合や、オブジェクトの状態や条件に応じて動作が異なる場合は、次のようにします。組成: 参照する状態そして戦略デザインパターン。
オブジェクトが同じ型である必要がある場合は、継承あるいはインターフェースを実装する。
Client
。それから、の新しい概念PreferredClient
後でポップアップします。すべきPreferredClient
受け継ぐClient
?優先クライアントは'です。クライアントafternl、いいえ?それほど速くはありませんが、実行時にオブジェクトがクラスを変更することはできません。どのようにモデル化しますかclient.makePreferred()
操作?おそらく答えは、欠けている概念、Account
おそらく? - plalxClient
クラスの概念をカプセル化したクラスが1つだけAccount
これはStandardAccount
またはPreferredAccount
... - plalx
私は個人的には継承よりも合成を好むことを学びました。コンポジションでは解決できない、継承で解決できるプログラム上の問題はありません。ただし、場合によっては、インタフェース(Java)またはプロトコル(Obj-C)を使用する必要があります。 C ++はそのようなことを何も知らないので、抽象基底クラスを使わなければなりません。つまり、C ++では継承を完全に取り除くことはできません。
コンポジションは論理的であることが多く、抽象化、カプセル化、コードの再利用を改善し(特に大規模プロジェクトで)、コード内のどこかに独立した変更を加えたからといって遠く離れてもうまくいきません。それはまたそれを容易に支持することを可能にします」単一責任原則「これはよく要約される」クラスが変更される理由が複数あることはありません。「そして、すべてのクラスは特定の目的のために存在し、その目的に直接関連するメソッドのみを持つべきであることを意味します。多くの人が、継承は私たちの現実の世界それは実はよくありません。現実の世界は、継承よりもはるかに多くの構成を使用しています。あなたがあなたの手に持つことができるほとんどすべての現実世界のオブジェクトは他のより小さな現実世界のオブジェクトから構成されています。
ただし、構成には欠点があります。継承を完全にスキップして合成のみに焦点を当てると、継承を使用していた場合には不要だった余分なコード行を2、3行作成しなければならないことに気づくでしょう。あなたはまた時々自分自身を繰り返すことを余儀なくされ、これはDRYの原則(DRY =自分自身を繰り返さないでください)。また、コンポジションはしばしば委任を必要とし、メソッドはこの呼び出しを囲む他のコードなしで他のオブジェクトの他のメソッドを呼び出すだけです。そのような「二重メソッド呼び出し」(これは、三重または四重メソッド呼び出しに拡張することができ、それよりはるかに遠いこともあります)は、単純に親のメソッドを継承する継承よりもはるかにパフォーマンスが悪くなります。継承されたメソッドを呼び出すことは、継承されていないメソッドを呼び出すことと同じくらい速いかもしれませんし、あるいは少し遅いかもしれませんが、通常は2つの連続したメソッド呼び出しより速いです。
あなたは、ほとんどのオブジェクト指向言語が多重継承を許さないことに気づいたかもしれません。多重継承が本当にあなたに何かを買うことができるケースがいくつかありますが、それらはルールよりもむしろ例外です。 「多重継承はこの問題を解決するための本当にクールな機能だ」と思うような状況に遭遇したときはいつでも、継承を完全に再考する必要がある時点にいます。コンポジションをベースにしたソリューションは、通常、はるかにエレガントで柔軟性があり、将来性のある証明になります。
継承は本当にクールな機能ですが、ここ数年で使われ過ぎてきたようです。それが実際には釘なのか、ネジなのか、あるいはまったく違うものなのかにかかわらず、人々はそれをすべて釘付けにすることができる一つのハンマーとして継承を扱いました。
ここに満足のいく答えが見つからなかったので、私は新しいものを書きました。
その理由を理解するために」好む"継承の上の構成"、私たちは最初にこの短縮された慣用句で省略された仮定を取り戻す必要があります。
継承には2つの利点があります。サブタイプとサブクラス
サブタイピングタイプ(インターフェース)シグニチャ、すなわち一組のAPIに準拠することを意味し、サブタイプポリモーフィズムを達成するためにシグニチャの一部をオーバーライドすることができる。
サブクラス化メソッド実装の暗黙の再利用を意味します。
この2つの利点には、継承を実行するための2つの異なる目的があります。サブタイプ指向とコード再利用指向です。
コードの再利用が唯一つまり、親クラスのパブリックメソッドの中には、子クラスにとってあまり意味がないものがあります。この場合、継承よりも合成を優先するのではなく、合成は要求された。これはまた、 "is-a"対 "has-a"の概念が由来するところです。
したがって、サブタイプが意図されている場合、つまり、後で新しいクラスを多態的に使用する場合にのみ、継承または合成を選択するという問題に直面します。これは、議論中の短縮された慣用句では省略されるという仮定です。
サブタイプとは型シグネチャに準拠することです。これは、コンポジションが常にその型のAPIをそれ以上公開しないことを意味します。これでトレードオフが始まります。
オーバーライドされていない場合、継承は単純なコードの再利用を提供しますが、単純な委任の仕事であっても、コンポジションはすべてのAPIを再コーディングする必要があります。
継承は直接的なものですオープン再帰内部多型サイト経由this
つまり、オーバーライドメソッドを呼び出す(あるいはタイプ)パブリックまたはプライベートの別のメンバー関数でがっかり)オープン再帰は合成によるシミュレーションしかし、それは余分な労力を必要とし、常に実行可能とは限りません(?)。この回答重複した質問に似たようなことを話します。
継承の公開保護されたメンバーこれは親クラスのカプセル化を破り、サブクラスで使用されている場合は、子とその親の間に別の依存関係が導入されます。
コンポジションは制御の逆転という利点があり、その依存関係は次のように動的に注入できます。デコレータパターンそしてプロキシパターン。
直後に構図インタフェースへのプログラミング。
構成は簡単の利点があります多重継承。
上記のトレードオフを念頭に置いて、好む相続以上の構成密接に関連したクラス、つまり暗黙のコード再利用が本当に利益をもたらす場合、またはオープン再帰の魔法の力が望まれる場合は、継承が選択となります。
私の一般的な経験則:継承を使用する前に、合成がより意味があるかどうかを検討してください。
理由:サブクラス化は通常、より複雑さと接続性を意味します。つまり、間違いを犯さずに変更、維持、および拡張を行うのは困難です。
より完全で具体的なTim Boudreauからの回答太陽の
私が見ているような継承の使用に関する一般的な問題は次のとおりです。
- 罪のない行為は予想外の結果をもたらす可能性があります - この典型的な例は、スーパークラスからオーバーライド可能なメソッドへの呼び出しです。 サブクラスインスタンスフィールドの前のコンストラクタ 初期化されました。完璧な世界では、だれもそれをしないでしょう。これは 完璧な世界ではありません。
- それはサブクラスがメソッド呼び出しの順序などについて仮定を立てるというひどい誘惑を提供します - そのような仮定はしない傾向がある スーパークラスが時間の経過とともに進化する可能性がある場合は安定している必要があります。また見なさい私のトースター とコーヒーポットの例え。
- クラスが重くなる - あなたは、あなたのスーパークラスがそのコンストラクタでどんな仕事をしているのか、あるいはそれがどれくらいのメモリを行っているのかを必ずしも知らない。 使用する。そのため、いくつかの無実の軽量オブジェクトを構築することができます。 あなたが思うよりはるかに高価である、そしてこれは時間の経過とともに変わるかもしれない スーパークラスが進化する
- それはサブクラスの爆発を促進します。クラスローディングは時間がかかり、クラスを増やすとメモリがかかります。あなたがするまでこれは問題ないかもしれません NetBeansの規模のアプリケーションを扱うことができました たとえば、最初の表示が遅いためにメニューが遅くなるなどの問題 メニューが大量のクラスロードを引き起こした。に移動することでこれを修正しました より宣言的な構文や他のテクニックが、それには時間がかかります 同様に修正してください。
- それは後で物事を変えることを難しくします - あなたがクラスをパブリックにした場合、スーパークラスを交換するとサブクラスが壊れます - それは、あなたがコードを公開したら、あなたは結婚しているという選択です。 に。実際の機能を自分のものに変えていないのであれば スーパークラスであれば、後で自由に変更することができます。 必要なものを拡張するのではなく、使用してください。例えば、 JPanelのサブクラス化 - これは通常間違っています。そしてサブクラスが どこかで公の場では、あなたはその決定を再検討する機会を得ることはありません。もし それはJComponent getThePanel()としてアクセスされます、あなたはまだそれをすることができます(ヒント: APIとしてのコンポーネントのモデルを公開します。
- オブジェクト階層は拡張されません(または、後で拡張することは、前もって計画するよりもはるかに困難です)。 - これは古典的な「多すぎるレイヤー」です 問題。以下に、そしてAskTheOracleパターンがどのようにできるのかを説明します。 それを解決する(それはOOP純粋主義者を害するかもしれないが)。
...
継承を許可している場合はどうすればよいですか。 塩の粒と一緒に取る:
- 定数以外はフィールドを公開しない
- 方法は抽象的または最終的でなければならない
- スーパークラスのコンストラクタからメソッドを呼び出さない
...
これはすべて、大規模プロジェクトよりも小規模プロジェクトにはあまり当てはまりません。 パブリッククラスよりプライベートクラスへ
継承はサブクラスとスーパークラスの間に強い関係を作ります。サブクラスはスーパークラスの実装の詳細を知っていなければなりません。スーパークラスをどのように拡張できるかを考えなければならない場合、スーパークラスを作成するのははるかに困難です。クラスの不変式を慎重に文書化し、他のオーバーライド可能なメソッドが内部で使用しているものを説明する必要があります。
階層が実際にis-a-relationを表している場合、継承は時々役に立ちます。それは、クラスは修正のためにクローズされるべきであるが拡張のためにオープンであるべきであると述べるオープンクローズド原理に関連します。そうすれば、多態性を持つことができます。スーパータイプとそのメソッドを扱う一般的なメソッドを持つために、動的なディスパッチを通してサブクラスのメソッドが呼び出されます。これは柔軟性があり、(実装の詳細についてあまり知らないために)ソフトウェアに不可欠な間接指定を作成するのに役立ちます。
ただし、継承は簡単に使いすぎになり、クラス間の依存関係が厳しくなり、複雑さが増します。また、プログラムの実行中に何が起こるかを理解することは、レイヤとメソッド呼び出しの動的選択のためにかなり困難になります。
デフォルトとして構成を使用することをお勧めします。よりモジュール化されており、遅延バインディングの利点があります(コンポーネントを動的に変更できます)。また、個別にテストする方が簡単です。そして、もしあなたがクラスからのメソッドを使う必要があるなら、あなたは特定の形式になることを強制されません(Liskov Substitution Principle)。
Inheritance is sometimes useful... That way you can have polymorphism
継承とポリモーフィズムの概念をハードリンクする(サブタイプはコンテキストを前提としていると仮定)。私のコメントは、あなたが自分のコメントで明確にしていることを指摘することを意図していました。つまり、継承は多態性を実装する唯一の方法ではなく、実際にコンポジションと継承を決定するときの決定要因ではありません。 - BitMask777
他の答えを見てください。
いつでも文「バーは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は継承できないため、継承を使用しないでください。言い換えれば、継承はプロパティの共有ではなく、機能の共有に関するものです。伸びる基本クラスの機能は制限しません。
これは、リスコフ代用原則:
基本クラスへのポインタまたは参照を使用する関数は、それを知らなくても派生クラスのオブジェクトを使用できなければなりません。
computeArea(Circle* c) { return pi * square(c->radius()); }
。楕円を渡すと明らかに壊れています(radius()はどういう意味ですか?)楕円は円ではないため、円から派生してはいけません。 - BoriscomputeArea(Circle *c) { return pi * width * height / 4.0; }
現在は一般的です。 - Fuzzy Logicwidth()
そしてheight()
?今、図書館利用者が" EggShape"という別のクラスを作成することにした場合はどうなりますか。 「Circle」からも派生するはずですか。もちろん違います。卵形は円ではなく、楕円形も円ではありません。そのためLSPを破るのでCircleから派生するものはありません。 Circle *クラスで演算を実行するメソッドは、円とは何かについて強い仮定を立てています。これらの仮定を破ると、ほぼ間違いなくバグが発生します。 - Boris
航空機には、エンジンと翼の2つの部品しかないとします。
それから航空機クラスを設計する2つの方法があります。
Class Aircraft extends Engine{
var wings;
}
今あなたの航空機は固定翼を持つことから始めることができます
そしてそれらをその場で回転翼に変える。それは本質的に
翼を持つエンジン。しかし、私が変更したい場合はどうなりますか
エンジンもその場で?
どちらかの基本クラスEngine
変更するミューテーターを公開する
プロパティ、または私は再設計Aircraft
として:
Class Aircraft {
var wings;
var engine;
}
これで、エンジンをその場で交換することができます。
「継承よりも合成を優先する」という設計原則は、継承が適切でない場合には継承を悪用しないでください。
2つのエンティティ間に実世界の階層関係が存在しない場合は、継承を使用せず、代わりに合成を使用してください。構成は「HAS A」の関係を表します。
例えば。車には車輪、車体、エンジンなどがあります。ただし、ここで継承している場合、車は車輪になります - これは誤りです。
詳しくは、を参照してください。継承よりも合成を好む
基本クラスのAPIを「コピー」/公開したい場合は、継承を使用します。機能を「コピー」するだけの場合は、委任を使用してください。
この一例としては、リストからスタックを作成したいとします。スタックはポップ、プッシュ、ピークのみです。 push_back、push_front、removeAtなどのような機能をスタックに入れたくない場合は、継承を使用しないでください。
これら2つの方法は、うまく共存し、実際に互いに支え合うことができます。
コンポジションは単にモジュール式に再生されます。親クラスに似たインターフェースを作成し、新しいオブジェクトを作成し、それに呼び出しを委任します。これらのオブジェクトが互いを知る必要がない場合は、非常に安全で使いやすいコンポジションです。ここにはたくさんの可能性があります。
しかし、何らかの理由で親クラスが経験の浅いプログラマ用の「子クラス」によって提供される関数にアクセスする必要がある場合は、継承を使用するのに最適な場所のように見えます。親クラスは、サブクラスによって上書きされるそれ自身の抽象 "foo()"を呼び出すだけで、抽象基底に値を渡すことができます。
これはいい考えのように見えますが、多くの場合、単に必要なベースクラスから新しいクラスを継承するよりも、単にfoo()を実装するオブジェクトを(またはfoo()から提供される値を手動で設定する)オブジェクトに渡すほうが良いです。指定する関数foo()
どうして?
継承は情報を移動するための悪い方法です。
コンポジションはここで本当の強みを持っています:関係は逆にすることができます: "親クラス"または "抽象ワーカー"は特定のインターフェースを実装する特定の "子"オブジェクトを集約することができます+どんな子でも他のどの種類の親の中にも設定できます。。また、MergeSortやQuickSortは抽象比較インターフェイスを実装するオブジェクトのリストを並べ替えることができます。言い換えれば、 "foo()"を実装しているオブジェクトのグループと、 "foo()"を持っているオブジェクトを利用できる他のオブジェクトのグループは、一緒にプレイすることができます。
継承を使用する3つの本当の理由を考えることができます。
もしこれらが真実なら、おそらく継承を使う必要があるでしょう。
理由1を使用して悪いことは何もありません、それはあなたのオブジェクトのしっかりしたインターフェースを持つことは非常に良いことです。これは、コンポジションを使用しても継承を使用しても実行できます。このインターフェイスが単純で変更されない場合は問題ありません。通常、継承はここではかなり効果的です。
理由が2の場合、それは少しトリッキーになります。本当に同じ基本クラスを使うだけでいいのですか?一般に、同じ基本クラスを使用するだけでは十分ではありませんが、それはあなたのフレームワークの要件であり、避けられない設計上の考慮事項です。
しかし、あなたがプライベート変数を使いたい場合、ケース3、そしてあなたは問題を抱えているかもしれません。グローバル変数を安全でないと考えるなら、プライベート変数へのアクセスを安全にするために継承を使うことを考えるべきです。。ご存知のとおり、グローバル変数はすべて悪いわけではありません - データベースは本質的にグローバル変数の大きなセットです。しかし、あなたがそれを扱うことができれば、それはそれで結構です。
それ以外にも考慮すべき点があります、あなたのオブジェクトが通過しなければならない継承の「深さ」も考慮しなければなりません。継承レベルが5または6レベルを超えると、予期しないキャストやボクシング/アンボクシングの問題が発生する可能性があります。その場合は、代わりにオブジェクトを作成することをお勧めします。
これを理解する簡単な方法は、同じクラスを持つクラスのオブジェクトが必要なときに継承を使うべきだということです。インタフェースそのため、その親クラスとして、親クラスのオブジェクトとして扱うことができます(アップキャスト)。さらに、派生クラスオブジェクトの関数呼び出しはコード内のどこでも同じままですが、呼び出す特定のメソッドは実行時に決定されます(つまり、低レベルの実装異なる、高レベルインタフェース同じまま)。
新しいクラスが同じインターフェースを持つ必要がない場合、つまりそのクラスのユーザーが知っている必要がないクラスの実装の特定の側面を隠したい場合は、コンポジションを使用する必要があります。そのため、構成はサポートの方がよりカプセル化継承はサポートを目的としていますが(つまり、実装を隠す)抽象化(つまり、何かの簡略化された表現、この場合は同じ内部構造が異なるさまざまなタイプのインタフェース)
あなたが持っているときです2つのクラスの間の関係(例犬は犬です)、あなたは相続のために行きます。
一方であなたが持っているときがありますまたは2つのクラス(学生がコースを持っている)または(教師研究コース)間の何らかの形容詞の関係、あなたは作文を選びました。
@Pavelに同意する、彼が言うとき、構成のための場所があり、継承のための場所がある。
あなたの答えがこれらの質問のどれにでも肯定的であるならば、私は相続が使われるべきであると思います。
ただし、純粋にコードを再利用することを意図しているのであれば、合成はおそらくより良い設計上の選択です。
私が聞いたことのある経験則では、継承は「ある」関係であるときは継承を使用し、「ある」ときは構成を使用する必要があります。それでも、私はあなたが常に作曲に傾くべきであると感じます。
継承はコードの再利用にとって非常に強力なメカニズムです。しかし、適切に使用する必要があります。サブクラスが親クラスのサブタイプでもある場合、継承は正しく使用されていると言えます。前述のように、ここでの重要なポイントは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)メソッドの動作は、親型のプロパティを保持するのではなく、明確に変更されました。あなたのクラスのクライアントの観点から考えてください。それらは整数のリストが期待されるところで整数のセットを受け取るかもしれません。たとえその値がすでにリストに存在していても、クライアントは値を追加してその値をリストに追加したいかもしれません。しかし、値が存在する場合、彼女はそのような動作をしません。彼女にとって大きな驚きです。
これは継承の不適切な使用の典型的な例です。この場合はコンポジションを使用してください。
(からのフラグメント:継承を正しく使う)
コンポジションv / s継承は幅広い主題です。私はそれがすべてシステムの設計に依存していると思うので、何がより良いのかについて真の答えはありません。
一般的にオブジェクト間の関係のタイプはそれらの一つを選ぶためのより良い情報を提供します。
関係タイプが「IS-A」関係である場合、継承はより良いアプローチです。 それ以外の場合、関係タイプは "HAS-A"関係であり、構成はより適切なアプローチになります。
それは実体的な関係に完全に依存します。
新しいプログラマーのために別の観点からこの質問に取り組むには:
オブジェクト指向プログラミングを学ぶとき、継承は早くから教えられることが多いので、それは一般的な問題に対する簡単な解決策と見られています。
私は3つのクラスがあり、それらすべてに共通の機能が必要です。だから私は 基底クラスを書いて、それらすべてから継承するようにすれば、 すべてがその機能を持っていて、私は一度にそれを維持する必要があるでしょう 場所。
それは素晴らしいように思えますが、実際にはいくつかの理由のうちの1つのために、実際には動作しません。
結局、私たちは自分のコードをいくつかの難しい結び目に結び付けていて、「クール、継承について学んだので今はそれを使いました」と言う以外は何もしません。私たち全員がそれをやったからといって、納得のいくものではありません。しかし、誰も私たちにしないように言わなかったので私たち全員がそれをしました。
誰かが「継承よりも合成を支持する」と説明した直後に、継承を使用してクラス間で機能を共有しようとするたびに考え直し、ほとんどの場合それが実際にうまく機能しないことに気付きました。
解毒剤は単一責任原則。それを制約と考えてください。私のクラスしなければならない一つのことをする。私しなければならない私のクラスに、そのことの1つを説明する名前を付けることができます。 (すべてに例外がありますが、学習しているときは絶対規則の方が優れている場合があります。)という基本クラスを書くことはできません。ObjectBaseThatContainsVariousFunctionsNeededByDifferentClasses
。私が必要とするどんな明確な機能もそれ自身のクラスになければならず、そしてその機能を必要とする他のクラスはそのクラスに依存することができますではないそれから継承します。
単純化し過ぎる危険性があるので、それは構成 - 一緒に機能するように複数のクラスを構成することです。そしてその習慣を形成すると、継承を使うよりもはるかに柔軟で、保守しやすく、テストしやすいことがわかります。
多くの人が言ったように、私は最初にチェックから始めます - "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つの直交する目的に使用できるということです。
インターフェース(多態性用)
実装(コード再利用のため)
参照
作曲が好まれるとしても、私は長所を強調したいと思います。継承との短所組成。
継承の長所:
それは論理を確立します「Aです」関係。もし車そしてトラック2種類あります車両(基本クラス)、子どもクラスAです基本クラス
すなわち
車は車です
トラックは車です
継承により、機能を定義/修正/拡張することができます
コンポジションの短所
例えばもし車含む車両そしてあなたがの価格を取得する必要がある場合車で定義されている車両あなたのコードはこのようになります
class Vehicle{
protected double getPrice(){
// return price
}
}
class Car{
Vehicle vehicle;
protected double getPrice(){
return vehicle.getPrice();
}
}