継承よりも合成


13

私は自分自身にソフトウェア工学を教えようとしていますが、私を混乱させる矛盾する情報に出くわします。

私はOOPと抽象クラス/インターフェースとは何か、そしてそれらをどのように使用するかを学びましたが、「継承よりも合成を優先する」べきだと読んでいます。構成とは、あるクラスが別のクラスのオブジェクトを構成/作成して、その新しいオブジェクトの機能を利用/対話することです。

だから私の質問は...抽象クラスとインターフェイスを使用しないはずですか?抽象クラスを作成せず、その抽象クラスの機能を具象クラスで拡張/継承し、代わりに、新しいオブジェクトを作成して他のクラスの機能を使用するだけですか?

または、構成を使用し、抽象クラスから継承することになっています。両方を一緒に使用しますか?もしそうなら、それがどのように機能し、その利点のいくつかの例を提供できますか?

私はPHPに最も慣れているので、他の言語に移り、新しく習得したSEスキルを移転する前に、それを使用してOOP / SEスキルを向上させています。


2
「継承よりも合成を好む」というのは、「ドリルよりも好意的な見方」とまったく同じくらい理にかなっている愚かな考えです。これらは2つの異なるユースケースでの使用に適した2つの異なるツールであり、どちらか一方を実際に交換可能に使用できる場合は実際には非常にまれです。
メイソンウィーラー

2
「Y以上の恩恵Xは」Xが良いだろう場合だけを考え、Yを使用したことがないという意味ではありません
リチャード・チンクル

2
「継承よりもお気に入りの構成」は、「クラスの継承を使用しない」とより正確に表現されます。インターフェースの実装は継承ではなく、選択した言語がインターフェースではなく抽象クラスのみをサポートし、抽象クラスも「継承ではない」と見なすことができます。
デビッドアルノ

1
@MasonWheeler、継承の有効なユースケースはありません。少なくとも「goto」と同じくらい「悪」な機能です。
デビッドアルノ

1
@DavidArnoそれはとんでもないことです。継承は、プログラミングの歴史全体でこれまでに開発された最も有用で生産的な機能の1つです。便利なものと同様に、それを悪用する方法はたくさんありますが、適切に使用すると、作業の能力と生産性が大幅に向上します。
メイソンウィーラー

回答:


19

継承を介した構成とは、既存のクラスの機能を再利用または拡張する場合、既存のクラスを「ラップ」してその実装を内部で使用する別のクラスを作成する方が適切な場合が多いことを意味します。デコレータパターンはこの例です。

継承は、再利用シナリオに対処するための適切なデフォルトの方法ではありません。多くの場合、基本クラスの機能の一部のみを使用したいため、サブクラスはリスコフ置換を満たす方法で基本クラスのコントラクト全体をサポートできないためです原則

テンプレートメソッドは、継承が適切な場合の良い例です。

構成と継承を選択する方法に関するガイドラインについては、この回答を参照してください。


+1。クラスの部分的な再利用の場合、未使用の部分は継承よりも構成の方がきれいに無視されます。
ローレンス

4

その言語で具体的な例を挙げられるほどPHPに精通していませんが、ここではいくつかの有用なガイドラインを紹介します。

インターフェイス/特性は、共通点がほとんどない多くのクラスで動作を定義するのに役立ちます。

たとえば、JSON APIで作業している場合、2つのメソッド「toJson」と「toStatusCode」を指定するインターフェースを定義できます。これにより、このインターフェイスを実装するオブジェクトを取得し、Http応答に変換できるヘルパーを作成できます。

ほとんどの場合、これは直接継承よりも好ましい方法です。なぜなら、それは浅いクラス階層に向かう傾向があるため、脆弱な基本クラスの問題に苦しむ可能性が低いためです。

直接継承は、やむを得ない理由がない限りあなたがすることよりも、そうするためのやむを得ない理由があるときに行うことと最もよく考えられます。

インターフェースのデフォルト実装をサポートしない言語では、基本機能のセットを共有するのが合理的な方法かもしれませんが、通常はサブクラスでできることを大きく制限します(利用可能な場合、多重継承はめったに価値がありません) 。多くの場合、代わりにコンポジションを使用する定型的なリダイレクトメソッドを記述するのは苦労に値することがわかります。

構成はデフォルトの戦略である必要があります。可能な限り、インターフェイスで作成し、コンストラクター引数として渡します。これにより、テストを書くときにあなたの人生がずっと楽になります。

出力の一部がシステムクロックの状態に依存している場合、予想される出力と実際の出力を比較するのは苦痛なので、可能な限りグローバルシングルトンを使用しないでください。

もちろん、これは常に可能とは限りません。たとえば、Play Webフレームワークは、基本的にドメイン固有の言語であるJSONライブラリを提供します。これを変更するのは大きな仕事であり、「Json.prettyPrint」への呼び出しを置き換えることは非常に小さな部分にすぎません。


1

構成とは、クラスから継承するのではなく、既にこの機能を実装している(おそらく内部の)クラスをインスタンス化することにより、クラスが機能を提供することです。

たとえば、船をモデル化するクラスがあり、船がヘリポートを提供するように指示されている場合、ヘリポートから船を派生させるのは自然ではありません(代わりに!)船にはヘリポートクラスが含まれており、何らかのShip.getHelipad()メソッドで公開しています。

数年前(10年ほど前)に人々は機能を集約するための迅速かつ簡単な方法として継承を見ていました。

しかし、「継承よりも好意的な構成」の言葉は、これが単なる提案であり、規則ではないことを明確にするために慎重に表現されています。の作者は、「あなたは決して継承を使わず、作曲のみ」というようなことを言わないように十分に注意していました。これは基本的に、ソフトウェアエンジニアリングコミュニティの関心を引き継いでいますが、多くの場合、コンポジションは継承よりも明確でエレガントで保守可能なデザインを生成します。

したがって、本質的に、「継承よりもお気に入りの構成」の原則は、「継承するか、構成するか」に直面したときはいつでも示唆しています。質問、あなたは最も適切な戦略は何であるか一生懸命に考えるべきであり、最も適切な戦略は継承ではなく構成であることが判明するということです。

ただし、これはルールではないため、継承がより自然な場合が多くあることにも留意してください。継承を使用する必要がある場所で構成を使用すると、コードに多くの悪が生じます。

船の例に戻ると、船がとやり取りするためのインターフェースを提供する必要がある場合FloatingMachine、抽象FloatingMachineクラスから派生する方が自然Machineです。

構成対継承の質問に対する答えの経験則は次のとおりです。

私のクラスは、公開する必要があるインターフェースと「関係」がありますか?はいの場合、継承を使用します。そうでない場合は、構成を使用します。

船は「浮遊」機であり、浮遊機は「浮遊」機です。したがって、継承はそれらにとって完全に問題ありません。しかし、船はもちろんヘリポートではありません。そのため、ヘリパッドの機能をより良く構成します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.