戦略パターンと依存性注入を使用して継承を完全に置き換えることはできますか?


10

例えば:

var duckBehaviors = new Duckbehavior();
duckBehaviors.quackBehavior = new Quack();
duckBehaviors.flyBehavior = new FlyWithWings();
Duck mallardDuck = new Duck(DuckTypes.MallardDuck, duckBehaviors)

Duckクラスにはすべての動作(抽象)が含まれているため、新しいクラスMallardDuck(拡張Duck)を作成する必要はないようです。

参照:ヘッドファーストデザインパターン、第1章。


Duckbehavior.quackBehaviorコードのタイプとその他のフィールドは何ですか?
max630

この例では、依存関係の注入はありません。
デビッドコンラッド

11
継承は、リファクタリングト​​リックとその周りのデザインパターンの長い歴史があるため、中小規模の開発者にとっては素晴らしいものです。しかし、私が話したほとんどの経験豊富な開発者は、可能な限り、構成に基づいた非常に浅い継承階層を好むようにしています。継承は、必要以上に密結合を引き起こし、変更を困難にする可能性があります。
Mark Rogers


5
@DavidConrad:OPは、クラス内で新規作成を行っていません。DI / IoCは依存関係を注入することであり、コンテナーではなく、「新規」を使用しないことです。それは完全に直交する問題です。その上、あなたはいつもどこかでコードを変更しなければなりません- それはコンポジションルートか何かの設定ファイルです。依存関係の作成を制御するのはDuckのctorではなく、一部の外部コンテキストであるため、ここではDuckタイプ内でコントロールが反転しています。これは明快さのために与えられたおもちゃの例であり、外部コンテキストが呼び出しコードだけで表されていることは完全に問題ありません。
FilipMilovanović19年

回答:


21

もちろん、その構成と委任を呼び出します。戦略パターンと依存性注入は構造的に似ているように見えるかもしれませんが、それらの意図は異なります。

戦略パターンを使用すると、同じインターフェースで動作をランタイムで変更できます。私はマガモに飛ぶように言って、羽ばたくのを見ることができました。次に、それをジェットパイロットのアヒルと交換して、デルタ航空で飛ぶのを見てください。プログラムの実行中にそれを行うのは戦略パターンのことです。

依存性注入は、ハードコーディングの依存関係を回避して、クライアントが変更されたときにクライアントを変更することなく、独立して変更できるようにする手法です。クライアントは、彼らがどのように満たされるかを知らなくても、単に彼らのニーズを表明します。したがって、それらがどのように満たされるかは、他の場所(通常はメイン)で決定されます。この手法を利用するために2羽のカモは必要ありません。どのアヒルを知らないか気にせずにアヒルを使うものだけです。アヒルを構築したり探したりはしませんが、手にしたアヒルを何でも使って喜んでくれるもの。

具体的なアヒルのクラスがある場合は、フライの動作を実装することができます。状態変数に基づいて、fly-with-wingsからfly-with-Deltaに動作を切り替えることもできます。その変数は、ブール値、int、またはifでテストすることなく、あらゆる飛行スタイルを実行FlyBehaviorするflyメソッドを持つa である可能性があります。これで、アヒルのタイプを変更せずに飛行スタイルを変更できます。マガモはパイロットになることができます。これは構成と委任です。アヒルはFlyBehaviorで構成されており、飛行要求をそれに委任できます。この方法ですべてのアヒルの動作を一度に置き換えるか、各動作または何かの間の任意の組み合わせに対して何かを保持できます。

これにより、継承には1つを除いて同じ力がすべて与えられます。継承を使用すると、DuckサブタイプでオーバーライドしているDuckメソッドを表現できます。構成と委任では、最初からサブタイプに明示的に委任する必要があります。これははるかに柔軟ですが、より多くのキーボード入力を必要とし、Duckはそれが起こっていることを知る必要があります。

ただし、継承は最初から明示的に設計する必要があると多くの人が考えています。継承されていない場合は、継承を許可しないようにクラスをシール/最終としてマークする必要があります。あなたがその見方をすると、継承は本当に構成と委任に勝るものはありません。それは、どちらの方法でも、最初から拡張性を考慮して設計するか、後で物事を分解していく必要があるためです。

物事を壊すことは実際に人気のあるオプションです。問題がある場合があることに注意してください。次のリリースで更新するつもりのないコードのライブラリーまたはモジュールを独立してデプロイした場合、現在の状況について何も知らないクラスのバージョンを処理することに行き詰まる可能性があります。

後で物事を分解してもかまわないということは、設計のやり直しから解放されますが、アヒルを使用したときに実際に何が行われるかを知らなくても、アヒルを使用して何かを設計できるという点で非常に強力です。知らないことは強力なものです。これにより、しばらくの間アヒルについて考えるのをやめ、コードの残りの部分について考えることができます。

「できるか」と「すべきか」は異なる質問です。継承より構成を優先しても、継承を使用しないとは限りません。継承が最も理にかなっている場合がまだあります。私のお気に入りの例を紹介します。

public class LoginFailure : System.ApplicationException {}

継承を使用すると、より具体的でわかりやすい名前の例外を1行で作成できます。

作曲でそれをやってみてください、あなたは混乱を得るでしょう。また、継承の連鎖を再利用および促進するデータやメソッドがないため、継承のヨーヨー問題のリスクはありません。このすべてが良い名前です。良い名前の価値を過小評価しないでください。


1
良い名前の価値を過小評価しないでください」。皇帝の新しい服の時間:LoginExceptionは良い名前ではありません。それは古典的な「スマーフの命名」であり、私が取り除いた場合Exception、私が持っているのはすべてLogin、何が問題だったかを何も教えてくれません。
David Arno

悲しいことにException、すべての例外クラスの終わりを置くというとんでもない慣習に悩まされていますが、「それにこだわる」と「良い」を混同しないでください。
デビッドアルノ

@DavidArnoより良い名前を提案できるなら、私はすべて耳です。ここには、既存のコードベースの規則にとらわれないという利点があります。指一本で世界を変える力を持っていたら、何と名付けますか?
candied_orange

私は彼らに名前を付けたいStackOverflowOutOfMemoryNullReferenceAccessLoginFailureなど基本的には、名前オフ「例外」を取ります。また、必要に応じて、問題の原因を説明するように修正します。
David Arno

あなたのコマンドで@DavidArno。
candied_orange

7

ほとんどすべての方法論を他の方法論に置き換えても、機能するソフトウェアを作成できます。それでも、一部の問題は他の問題よりも特定の問題に適しています。

それは多くの事柄に依存しています。アプリケーションの先行技術、チームでの経験、期待される将来の発展、個人的な好み、そしていくつか例を挙げると、新人が頭を悩ませるのはどれほど難しいでしょう。

あなたがより経験を積み、他の人々の創造物により頻繁に苦しむにつれて、最後の熟考をより強調するようになるでしょう。

継承は依然として有効で強力なモデリングツールであり、必ずしも最も柔軟であるとは限りませんが、問題ドメインへの明確なマッピングに感謝している可能性がある新しい人々に強力なガイダンスを提供します。


-1

継承はかつて考えられていたほど重要ではありません。それはまだ重要であり、それを削除することは悪い間違いです。

極端な例は、すべてがNSObjectのサブクラスであるObjective-Cです(コンパイラーは、実際にはベースクラスを持たないクラスを宣言することを許可しないため、コンパイラーに組み込まれていないものすべて何かのサブクラスでなければなりませんコンパイラに組み込まれています)。NSObjectには、見逃してはいけない便利なものがたくさんあります。

もう1つの興味深い例は、UIKitのiOS開発用の基本的な「ビュー」クラスであるUIViewです。これは、一方ではサブクラスが埋める機能を宣言する抽象クラスに少し似ていますが、それ自体でも有用です。UIKitが提供するサブクラスがあり、そのまま使用されることがよくあります。ビューにサブビューをインストールする開発者による構成があります。また、多くの場合、開発者が定義したサブクラスがあり、それらはしばしばコンポジションを使用します。厳密な単一のルールやルールさえありません。要件に最も適したものを使用します。


あなたの「極端な例では、」最も近代的なオブジェクト指向言語がどのように機能するか大体です:PythonのサブクラスtypeからJava継承では、すべての非プリミティブObjectJavaScriptですべてが、で終わるのプロトタイプを持っているObjectなど、
Delioth

-1

[私は定評のある質問に対する生意気な答えを危険にさらします。]

戦略パターンと依存性注入を使用して継承を完全に置き換えることはできますか?

はい...ただし、戦略パターン自体は継承を使用します。戦略パターンは、インターフェースの継承で機能します。継承を構成+戦略で置き換えると、継承が別の場所に移動します。それにもかかわらず、階層を分離することができるので、そのような置換はしばしば行う価値があります


2
戦略パターンは、インターフェースの継承で機能します」。戦略パターンはデザインパターンであることを忘れないでください。実装パターンではありません。そのため、インターフェースを使用して実装できますが、関数のハッシュ/辞書などを使用して実装することもできます。
David Arno

3
インターフェイスの継承はまったく継承ではなく、単なるコントラクトまたは分類子です。デリゲートは戦略パターンにも使用できます(私はそれらを使用することをお勧めします)。
Deepak Mishra

プラス1、おい: ...インターフェースの継承、一般的な用語「インターフェース」の適切な使用に対する称賛。この質問が出される根本的な理由は、貧弱または無能なクラス設計にあると思います。本をとる理由/方法を説明するため。悪魔は詳細にあります。私は目を見張る喜びであると同時に、地獄である深い継承である継承デザインで働いてきました。私はinterface継承で固定された破壊的なデザインを見てきました。ここでの隠れた問題は、一貫性のない実装モーフィング依存動作です。P、S 継承とインターフェースは相互に排他的ではありません
–radarbob

@DavidArnoコメント:このコメントは、OPの質問に添付すると問題ありません。OPの質問は技術的に誤って記述されていますが、まだわかります。問題はこの特定の答えではありません。
レーダーボブ

@DeepakMishraコメント:。「インターフェース」は、クラスのすべてのパブリックメンバーです。残念ながら、「インターフェース」の意味は過負荷です。私たちは、プログラミング言語のキーワードで一般的な意味を区別するように注意する必要がありますinterface
radarbob

-2

いいえ。マガモのインスタンス化に別の種類のダックとは異なるパラメータが必要な場合、変更するのは悪夢になります。また、アヒルをインスタンス化できる必要がありますか?それはマガモまたはマンダリンのアヒル、あるいはあなたが持っている他の種類のアヒルです。

また、型の周りにいくつかのロジックが必要になる可能性があるため、型の階層の方が良いかもしれません。

コードの再利用が一部の機能で問題になる場合は、コンストラクターを介して関数を渡すことにより、コードを再構成できます。

繰り返しますが、それは本当にあなたのユースケースに依存します。アヒルとマガモだけの場合、クラス階層ははるかに単純なソリューションです。

しかし、これは戦略パターンを使用する例です:顧客クラスがあり、ハッピーアワー請求戦略または通常の請求戦略である可能性がある請求戦略(インターフェース)を渡したい場合、それを渡すことができますコンストラクタで。この方法では、2つの異なる顧客クラスを作成する必要がなく、階層も必要ありません。顧客クラスは1つだけです。

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