私は自分自身にソフトウェア工学を教えようとしていて、私を混乱させるいくつかの矛盾する情報に立ち向かいます。
私はOOPと、抽象クラス/インターフェースとは何か、およびそれらの使用方法を学習してきましたが、「継承よりも構成を優先する」必要があることを読んでいます。合成とは、あるクラスが別のクラスのオブジェクトを作成/作成して、その新しいオブジェクトの機能を利用/操作することです。
だから私の質問は...私は抽象クラスとインターフェイスを使用しないはずですか?抽象クラスを作成せず、具象クラスでその抽象クラスの機能を拡張/継承せず、代わりに、他のクラスの機能を使用するために新しいオブジェクトを作成するだけですか?
または、私は構成を使用し、抽象クラスから継承することになっていますか?両方を組み合わせて使用しますか?もしそうなら、それがどのように機能するか、そしてその利点のいくつかの例を提供できますか?
私はPHPに最も慣れているので、それを使用してOOP/SEスキルを向上させてから、他の言語に進み、新たに取得したSEスキルを転送します。例として、PHPいただければ幸いです。
継承より構成とは、既存のクラスの機能を再利用または拡張する場合、多くの場合、既存のクラスを「ラップ」して内部でその実装を使用する別のクラスを作成する方が適切であることを意味します。 デコレータパターン はこの例です。
継承は、基本クラスの機能の一部のみを使用することがよくあり、サブクラスは基本クラスのコントラクト全体を満足できる方法でサポートできないため、再利用シナリオを処理するための適切なデフォルトの方法ではありません- リスコフ置換原理 。
テンプレートメソッド は、継承が適切な場合の良い例です。
構成と継承のどちらを選択するかについてのガイドラインは、 この回答 を参照してください。
私はPHPでその言語での具体的な例を提供するのに十分に精通していませんが、ここに私が役立つとわかったいくつかのガイドラインがあります。
Interfaces/traitsは、他にほとんど共通点がない可能性のある多くのクラスにわたって動作を定義するのに役立ちます。
たとえば、JSON APIで作業している場合、「toJson」と「toStatusCode」の2つのメソッドを指定するインターフェースを定義できます。これにより、このインターフェイスを実装する任意のオブジェクトを取得し、それをHttp応答に変換できるヘルパーを作成できます。
ほとんどの場合、これは継承を指示するよりも望ましい方法です。これは、浅いクラス階層に向かう傾向があるため、脆弱な基本クラスの問題に悩まされる可能性が低いためです。
直接継承は、やむを得ない理由がない限り行うよりも、やむを得ない理由がある場合に行うこととして最もよく考えられています。
インターフェースのデフォルト実装をサポートしていない言語では、基本機能のセットを共有することは妥当な方法かもしれませんが、通常、サブクラスで実行できることを大幅に制限します(利用可能な場合、多重継承が苦労する価値はほとんどありません)。 。多くの場合、代わりにコンポジションを使用するためのボイラープレートリダイレクトメソッドを記述するのは骨の折れる価値があります。
構成がデフォルトの戦略になります。可能な限り、インターフェースで構成し、それらをコンストラクター引数として渡します。これは、テストを書くときにあなたの人生をはるかに簡単にします。
出力の一部がシステムクロックの状態に依存している場合は、予想される出力と実際の出力を比較するのは正しい痛みであるため、グローバルシングルトンでの作業はできるだけ避けてください。
もちろん、これは常に可能であるとは限りません。たとえば、Playウェブフレームワークは、基本的にドメイン固有の言語であるJSONライブラリを提供します。これを変更することは大きな仕事であり、「Json.prettyPrint」への呼び出しを置き換えることはごくわずかな部分に過ぎません。
構成とは、そのクラスから継承するのではなく、すでにこの機能を実装している(おそらく内部の)クラスをインスタンス化することによって、クラスがいくつかの機能を提供する場合です。
したがって、たとえば、船をモデル化するクラスがあり、船がヘリパッドを提供する必要があると言われている場合、ヘリパッドから船を派生させることは自然ではありません(そうですね!)船にはヘリポートクラスが含まれており、Ship.getHelipad()
メソッドを介してそれを公開します。
何十年も前(10年ほど前)には、継承を機能を集約するための迅速で簡単な方法と見なしていたため、「ヘリポートから継承する船」の例はたくさんありましたが、もちろん非常に不自由でした。
しかし、「継承よりも好意的な構成」という言葉は、これが単なる提案であり、規則ではないことを明確にするために注意深く述べられています。辞書の作者は、「あなたはnever継承を使用するonly構成」のようなことを言わないように十分注意していた。これは基本的に、継承が過度に使用されているという事実をソフトウェアエンジニアリングコミュニティに注目させていますが、多くの場合、コンポジションは継承よりも明確でエレガントで保守可能な設計を生み出します。
つまり、本質的に、「継承よりも好意的な構成」という言説は、「継承するか、構成するか」に直面したときはいつでも、それを示唆しています。質問、あなたは最も適切な戦略は何であるかをしっかり考えるべきであり、そして最も可能性が高いのは、最も適切な戦略が継承ではなく構成であることが判明することです。
ただし、これは規則ではないため、継承がより自然な場合が多いことにも留意してください。継承を使用する必要がある場所でコンポジションを使用すると、多くの悪があなたのコードに降りかかります。
船の例に戻ると、船がFloatingMachine
と対話するためのインターフェースを提供する必要がある場合、抽象FloatingMachine
クラスから派生する方が自然です。次に、別の抽象Machine
クラスから派生します。
これが、構成と継承の質問に対する答えの目安です。
私のクラスには、公開する必要のあるインターフェースとの「is a」関係がありますか?はいの場合、継承を使用します。そうでない場合は、コンポジションを使用します。
船は「浮かぶ」機械であり、浮かぶ機械は「浮かぶ」機械です。したがって、継承はそれらにとって完全に適切です。しかし、船はもちろんヘリポートではありません。したがって、ヘリポートの機能をよりよく構成できます。