この投稿は質問 https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance/11758048#comment15634305_11758048 に基づいています。
一部の人々は言った-「is-a」関係があるかどうかを確認してください。存在する場合は、継承を使用します。継承を使用するための2番目のチェックがあるはずだと思います。基本クラスが抽象である場合にのみ継承を使用します。基本クラスをインスタンス化する必要がある場合は、コンポジションを使用します。継承ではありません。
例1.会計士は従業員です。ただし、Employeeオブジェクトはインスタンス化できるため、継承は使用しません。 (ビジネスマンと話をすると、彼らはあなたに言うでしょう-会計士は従業員です。しかしOO世界では会計士は従業員ではありません(すべきではありません))
例:2. Bookは、SellingItemです。 SellingItemはインスタンス化できません-それは抽象的な概念です。したがって、継承を使用します。 SellingItemは、抽象基本クラス(またはC#のインターフェース)です。
私がここで探しているのは、私の議論に挑戦する例です。つまり、基本クラスは抽象的ではありませんが(基本クラスはインスタンス化できます)、継承が合成よりも優れているシナリオです。そのような例を教えていただけますか?
注:Bankドメイン、HRドメイン、Retailドメイン、またはその他の一般的なドメインに基づいたシナリオを提供できると、さらに役立ちます。
注:デザインでは、コードを完全に制御できると想定してください。つまり、外部APIを使用していません。また、私たちは最初から始めています。
私の主張が間違っていることを証明するシナリオを説明する例がある場合は、回答済みとマークします。合成と継承のどちらを使用するかについての単なる議論ではありません。
私は https://stackoverflow.com/questions/3351666/why-use-inheritance-at-all?lq=1 で@anonの回答をサポートしています
継承を使用する主な理由は、構成の形式としてではありません。それは、多態的な動作を取得できるようにするためです。ポリモーフィズムが必要ない場合は、継承を使用しないでください。
@MatthieuM。 Code Smell:Inheritance Abuse で言う
継承の問題は、2つの直交する目的に使用できることです。
インターフェース(ポリモーフィズム用)
実装(コードの再利用)
[〜#〜]参照[〜#〜]
コメントであなたはそれが「構成vs継承」についての話ではないと述べましたが、私はそれに戻らなければなりません。継承よりも構成の方が良いからです。デカップリングが常に良いことだとすると( 継承より構成を優先しますか? )。
実際には、常にそうとは限りません。 「is a」を「has a」に置き換えることができる場合や「has a」の方が有望に見える場合でも、「is a」には非常に優れた自動プロパティがあります。
ウィジェット階層を考慮してください。通常、寸法に関連するすべてのものはルートの奥深くに埋め込まれています。また、継承パスがどれほど長くても、インスタンスのSetBounds
をいつでも直接呼び出して、深いベースオブジェクトを予期するプロシージャにインスタンスを渡すことができます。変換とアクセス。
ただし、継承階層を構成階層に変換すると(技術的には常に可能です)、ディメンションを変更したり、特定のクラスのプロシージャを提供したりするには、構成ツリーの具体的な部分を指す必要があるため、どちらの場合も利便性が失われます。タイプ。比較する
MyForm.SetBounds(... // easy because of an "is a"-tree
MyForm.ScrollingWinControl.WinControl.Control.SetBounds //complex because of a "has a"-tree
または
GetSomeInfoOfControl(MyForm) // easy ...
GetSomeInfoOfControl(MyForm.ScrollingWinControl.WinControl.Control) //complex ...
したがって、より強力な結合の代償にもかかわらず、継承は管理と抽象化が非常に簡単な場合があります。
「is a」対「has a」の議論全体が私にとって意味をなさなかった。継承をいつ使用するかについては、良い定義がすでにあります。
q(x)をT型のオブジェクトxについて証明可能な特性とする。それからq(y)はS型のオブジェクトyについて証明可能でなければならないTのサブタイプです。
最初に気付くのは、博士号を持つ人々に簡単な問題を説明させることは一般的に悪い考えであるということです。
第二に、これは継承をいつ使用するかを正確に述べています。 Wikipediaの記事 を使用して、それを会計士/従業員の例に適用する場合:
Liskovの動作サブタイプの概念は、可変オブジェクトの代替可能性の概念を定義します。つまり、
Accountant
がEmployee
のサブタイプである場合、プログラム内のタイプEmployee
のオブジェクトは、変更せずにタイプAccountant
のオブジェクトで置き換えることができます。そのプログラムの望ましい特性(例えば、正確さ)の。
ウィキペディアの記事のPrinciples段落全体には、さらに興味深い原則がいくつか含まれています。
どうやらほとんどの答えは何らかの方法でLSPを参照しています。 Baqueta見逃しやすいリンクとして巧妙に偽装し、Geek私の回答を投稿する前のLSP秒を含めるように彼の投稿を編集しました。したがって、LSPは階層を作成する最も合理的な方法のようです。
あなたの原則が成り立たないいくつかの例:
WPFでは、具象 System.Windows.Controls.Button には次のサブクラスがあります。
JavaおよびC#では、すべてがobject
から直接または間接に拡張されますが、これは抽象的ではありません。
また、WPFでは、DatePickerTextBoxは(具体的な)TextBoxのサブクラスです。さらに多くの例があります。これらはLSPを尊重し、基本クラスは抽象的ではありません。
補足として。この原則を見つけた理由は、継承によってカップリングが導入されるため、階層を非常にフラットに保つことは一般的に良いことだと思います。 1レベルまたは2レベルの深さで十分です。抽象クラスは(ほとんど)常にどこかにサブクラス化されます。これは、クラスを抽象化する目的であり、他の人がそれを特殊化できるようにするためです。ほとんどの具象クラスは、いかなる方法でも特殊化する必要はありません。これが、例をまだ見つけていない理由かもしれません。
私がここで探しているのは、私の議論に挑戦する例です
たくさんの例:
Buttons。多くのGUIフレームワークでは、Viewは抽象クラスではありません-バニラビューを作成できます。しかし、Buttonなど、Viewのサブクラスもたくさんあります。
Mutableオブジェクト一部のフレームワーク。 Cocoa。不変のデータコンテナークラス(NSArray、NSString、NSDictionaryなど)と、それらの可変サブクラス(NSMutableArray、NSMutableStringなど)があります。 NSMutableArrayのインスタンスに対しては、NSArrayのインスタンスに対して実行できることを何でも実行できます。
Employee。 Accountantの例を借用すると、EmployeeクラスがPersonのサブクラスである可能性が高くなりますが、Personが抽象的である必要がある理由はありません。
Accountant。継承の代わりに構成をどのように使用できるか/使用する必要があるかを示すために多くの場合例が示されていますが、この場合、構成が常に最良の選択であるとは限りません。はい。「仕事」の側面を「従業員」から分離し、特定の仕事で従業員のインスタンスを構成することにより、会計士を作成できます。しかし、代わりに継承を使用することには十分な理由があるかもしれません。会計士は、たまたま数字を処理する従業員だけではありません。会計士は従業員の特定の種類であり、継承を使用してそれを反映したい場合があります。たとえば、AccountantクラスにProfessionalStandingインターフェイスを実装したり、利益相反の検出に役立つ機能を含めたりすることができます。また、Accountantクラスを作成すると、言語の型システムを使用して、Accountant以外の従業員がAccountantのみを使用する必要があるロールで使用されないようにすることができます。Project::addAuditor(Accountant& a);
継承を使用するかどうかの決定は、クラスが抽象かどうかにはほとんど関係ありません。それは実際にはその逆です。抽象クラスは、サブクラスの作成を強制するために使用され、一般的に重要な欠落機能が提供されるようにします。 クラスをインスタンス化できるという事実は、確かにサブクラス化を避けるべきだという意味ではありません。
挑戦的な例が必要だとは思いません。あなたの言うことは依存関係の逆転という名前の素晴らしい設計原則です。つまり、上位のコンポーネントが下位のコンポーネントに依存することはありません。下位のコンポーネントは、上位のコンポーネントのみに依存する必要があります。
すべてのルールと同様に、これにも例外がある場合があります。しかし、例外なくルールはありません!
クラスを抽象化することを選択するための良い経験則は実際にあると思います:わずかに異なる動作を持つクラスのバリアントがある場合(または必要性を予測できる場合)、多くの場合、抽象基本クラスを作成します。
結論としては、私の経験では、あなたが扱っている関係が本当にis-aである限り(たとえば、Accountant-Employeeの例は、 この答え )のように、継承よりも構成に適している場合、非抽象クラスから継承することは非常にまれです。
ルールを無視する必要がある場合の2つの例。どちらも、Poodle
を substitutable にして、Dog
にする必要があると想定しています。
Dog
は外部APIのクラスであり、IDog
が実装するPoodle
インターフェースはありません。Dog
は、すでに(テスト済みの)コードの数十の異なる場所にインスタンス化されています。確かに、これらはどちらも、より優れたオリジナルデザインが役立った可能性があるケースですが、現実の世界ではデザインは決して完璧ではありません。
具象クラスを継承することは問題なく、実際には非常に価値のある機能だと思います。
「is-a」ルールは、非常に単純なモデルとドメインにのみ適しています。私の見解では、クラスは動作を表し、状態が別のクラスになる可能性がある状態をカプセル化します。私たちのモデルに関連して、会計士、従業員、帳簿、販売アイテムはどのような振る舞いをしますか?それはあなたが尋ねるべき質問です。そしてそれに基づいて構成の継承を使用します。
また、実際のオブジェクトの多くは良い例ではありません。それらは通常、異なる動作の複合物であり、異なる実際のオブジェクト間で共有できるためです。多くの動物種がすべて4を歩きます。これは行動の良い例です。
継承によって既存の動作を拡張することは、ドメインのモデルを簡素化およびモジュール化する優れた方法です。また、歩行動作の例では、WoundedWalkクラスを拡張できます。これは、その名前のとおりです。
コードの再利用が必要な場合は、コンポジションより継承を使用します。継承は、クラスを非常に速く進化させるのに役立ちます。ただし、使いすぎないように注意してください。それが理にかなっている場合(つまり、両方のクラスのすべてのオブジェクト間に「IS-A」の関係があることが確実である場合にのみ、慎重に使用してください。サブクラスオブジェクトが Liskov Substitutionを尊重していることを確認する必要があります。原則 。それでも、サブクラスに値コンポーネントを追加し、スーパークラスにも値コンポーネントの概念がある場合、毎回良いアイデアになるとは限りません。From From Java:
オブジェクト指向の抽象化の利点を忘れない限り、equalsコントラクトを維持しながら、インスタンス化可能なクラスを拡張し、値コンポーネントを追加する方法はありません。
一方、コンポジションには継承よりも多くの利点があるため、たいていの場合、コンポジションは継承よりも優先されますが、それはあなたの質問ではありませんでした。 Springの*Dependency Injectionの原則*は主にこの前提に基づいています。