私は現在、SOLIDを理解しようとしています。したがって、依存関係の逆転の原則は、2つのクラスが直接ではなく、インターフェースを介して通信する必要があることを意味します。例:class A
にclass B
型のオブジェクトへのポインタが必要なメソッドがある場合、このメソッドは実際にはabstract base class of B
型のオブジェクトが必要です。これは開閉にも役立ちます。
私が正しく理解しているとすれば、私の質問はですallクラスの相互作用にこれを適用するのは良い習慣ですか、それともレイヤーの観点から考えてみるべきですか?
私が懐疑的である理由は、この原則に従うためにいくらかの代償を払っているからです。たとえば、機能Z
を実装する必要があります。分析の結果、機能Z
は機能A
、B
およびC
で構成されていると結論付けました。 facadeクラスZ
を作成します。これは、インターフェースを通じてクラスA
、B
を使用しますおよびC
。私は実装のコーディングを開始し、ある時点で、タスクZ
が実際には機能A
、B
およびD
で構成されていることに気付きました。ここで、C
インターフェース、C
クラスプロトタイプを廃棄し、D
インターフェースとクラスを個別に作成する必要があります。インターフェースがなければ、クラスだけを置き換える必要がありました。
つまり、何かを変更するには、1。呼び出し元2.インターフェース3.宣言4.実装を変更する必要があります。 python直接結合された実装では、実装を変更onlyする必要があります。
多くの漫画や他のメディアでは、善と悪の力は、しばしばキャラクターの肩に座っている天使と悪魔によって示されています。ここのストーリーでは、善悪の代わりに、SOLID=片方の肩に、YAGNI(あなたはもう必要ありません!).
最大限に活用されたSOLIDの原則は、巨大で複雑な、超構成可能なエンタープライズシステムに最適です。小規模な、またはより具体的なシステムでは、すべてをばかばかしく柔軟にすることは適切ではありません。抽象化に費やす時間は、メリットとしては証明されないためです。
具象クラスの代わりにインターフェースを渡すと、たとえば、ファイルからの読み取りをネットワークストリームに簡単に交換できるようになります。ただし、大量のソフトウェアプロジェクトの場合、そのような柔軟性は必要ありませんeverが必要になることはありません。具体的なファイルクラスを渡して1日だけ呼び出し、脳細胞を節約することもできます。 。
ソフトウェア開発の芸術の一部は、時間の経過とともに変化する可能性のあるものとそうでないものについての良い感覚を持っています。変更される可能性があるものについては、インターフェイスと他のSOLIDの概念を使用します。変更されないものについては、YAGNIを使用して具体的な型を渡すだけで、ファクトリクラスを忘れて、すべてのランタイムのフックアップや構成など、SOLID抽象化の多くを忘れます。私の経験では、YAGNIアプローチは実際よりもはるかに頻繁に正しいことが証明されています。
素人の言葉で:
DIPの適用は簡単で楽しい。最初の試みで設計を正しく行わないことは、DIPを完全に諦める十分な理由にはなりません。
一方、インターフェースとOODを使用したプログラミングは、時々陳腐化したプログラミング技術に喜びをもたらすことができます。
一部の人々はそれが複雑さを加えると言います、しかし私はopossiteが本当だと思います。小さなプロジェクトでも。テスト/モッキングが簡単になります。 case
ステートメントまたはネストされたifs
がある場合、コードの数が少なくなります。循環的な複雑さを軽減し、新鮮な方法で考えさせます。これにより、プログラミングは実際の設計と製造により似たものになります。
依存関係の逆転 を使用してください。
1つの極端な反例は、多くの言語に含まれる「文字列」クラスです。それは基本的な概念、本質的には文字の配列を表します。このコアクラスを変更できると仮定すると、内部状態を他のものと交換する必要がないため、ここでDIを使用しても意味がありません。
他のモジュールに公開されていない、またはどこでも再利用されていないモジュールの内部で使用されるオブジェクトのグループがある場合、DIを使用する価値はおそらくありません。
私の意見では、DIが自動的に使用される2つの場所があります。
モジュールで拡張用に設計。モジュールの全体的な目的がモジュールを拡張して動作を変更することである場合は、最初からDIを組み込むのが最適です。
コードの再利用を目的としてリファクタリングしているモジュール内。おそらく、何かを行うためにクラスをコーディングし、その後、リファクタリングを使用してそのコードを他の場所で活用でき、そうする必要があることを後で理解します。これは、DIおよびその他の拡張性の変更の優れた候補です。
ここでのキーは必要な場所で使用しますこれにより複雑さが増し、測定することを確認しますneed技術要件(ポイント1)または定量的コードレビュー(ポイント2)のいずれか。
DIは優れたツールですが、他のツールと同じように、使いすぎたり誤用したりする可能性があります。
*上記のルールの例外:レシプロソーはあらゆる作業に最適なツールです。問題が解決しない場合は、削除されます。永久に。
元の質問にはDIPの要点の一部が欠けているようです。
私が懐疑的である理由は、この原則に従うためにいくらかの代償を払っているからです。たとえば、機能Zを実装する必要があるとします。分析後、機能Zは機能A、B、Cで構成されていると結論付けます。ファサードクラスZを作成します。インターフェースを通じて、クラスA、B、Cを使用します。実装し、ある時点で、タスクZが実際には機能A、B、およびDで構成されていることに気づきました。次に、Cインターフェース、Cクラスプロトタイプを廃棄し、個別のDインターフェースとクラスを作成する必要があります。インターフェイスがなければ、クラスだけが置き換えられる必要があります。
DIPを真に活用するには、まずクラスZを作成し、クラスA、B、C(まだ開発されていない)の機能を呼び出すようにします。これにより、クラスA、B、CのAPIが提供されます。次に、クラスA、B、Cを作成して、詳細を入力します。クラスZを作成するときに、クラスZが必要とするものに完全に基づいて、必要な抽象化を効果的に作成する必要があります。クラスA、B、またはCが作成される前に、クラスZの周りにテストを作成することもできます。
DIPは、「高レベルのモジュールは低レベルのモジュールに依存するべきではありません。どちらも抽象化に依存する必要がある」と言っていることを思い出してください。
Zクラスに必要なものと、Zクラスが必要とするものを取得する方法を確認したら、詳細を入力できます。もちろん、クラスZに変更を加える必要がある場合もありますが、99%の場合はそうではありません。
Zが記述される前にA、B、Cが必要であることを理解したので、クラスDは決してありません。要件の変更は、まったく別の話です。
短い答えは「ほとんどない」ですが、実際には、DIPが意味をなさない箇所がいくつかあります。
オブジェクトを作成するのが仕事であるファクトリーまたはビルダー。これらは本質的に、IoCを完全に包含するシステムの「リーフノード」です。ある時点で、何かが実際にオブジェクトを作成する必要があり、それを実行するために他のものに依存することはできません。多くの言語では、IoCコンテナーがそれを行うことができますが、昔ながらの方法でそれを行う必要がある場合があります。
データ構造とアルゴリズムの実装。一般に、これらの場合、最適化する顕著な特徴(実行時間や漸近的な複雑さなど)は、使用されている特定のデータ型によって異なります。ハッシュテーブルを実装する場合は、リンクリストではなく、ストレージ用の配列で作業していることを本当に知る必要があります。配列を正しく割り当てる方法を知っているのはテーブル自体だけです。また、可変配列を渡さず、呼び出し元にハッシュテーブルの内容をいじることによってハッシュテーブルを分割させたくありません。
ドメインモデル クラス。これらはビジネスロジックを実装しますが、(ほとんどの場合)1つの実装だけが理にかなっています。 someドメインモデルクラスは、他のドメインモデルクラスを使用して構築される場合がありますが、これは通常、ケースバイケースです。ドメインモデルオブジェクトには、便利に模倣できる機能が含まれていないため、DIPにテスト性や保守性のメリットはありません。
外部APIとして提供され、他のオブジェクトを作成する必要があるオブジェクト。その実装の詳細を公開したくない。これは、「ライブラリ設計はアプリケーション設計とは異なる」という一般的なカテゴリに分類されます。ライブラリまたはフレームワークは内部でDIを自由に使用できますが、最終的には実際の作業を行う必要があります。たとえば、ネットワークライブラリを開発しているとします。あなたは本当にしないコンシューマがソケットの独自の実装を提供できるようにしたいです。内部的にソケットの抽象化を使用する場合がありますが、呼び出し元に公開するAPIは独自のソケットを作成します。
ユニットテスト、およびダブルテスト。フェイクとスタブは、1つのことを行うだけで、それを簡単に行うことになっています。依存関係の注入を行うかどうかを心配するのに十分複雑な偽物がある場合、おそらくそれはあまりにも複雑です(おそらく、あまりにも複雑すぎるインターフェースを実装しているためです)。
もっとあるかもしれません。これらは私がやや頻繁に見るものです。
非常に微小なレベルでDIPを適用している兆候がいくつかありますが、それは価値を提供していません。
これが表示されている場合は、Zに直接Cを呼び出してインターフェースをスキップする方がよいでしょう。
また、依存関係注入/動的プロキシフレームワーク(Spring、Java EE)によるメソッド装飾は、true SOLID DIP-これは、その技術スタックでのメソッド装飾の動作の実装の詳細に似ています。Java= EEコミュニティは、それを改善と見なします( 参照 )のようにFoo/FooImplのペアは必要ありません。対照的に、Pythonは、ファーストクラスの言語機能として関数装飾をサポートしています。
このブログ投稿 も参照してください。
常に依存関係を反転させると、すべての依存関係が上下逆になります。つまり、依存関係の結び目を持つ厄介なコードから始めた場合、それはまだ(本当に)そのままであり、反転されているだけです。ここで、実装のすべての変更でインターフェースも変更する必要があるという問題が発生します。
依存関係の逆転のポイントは、あなたが選択的に物事をもつれさせている依存関係を反転させることです。 AからBに移動する必要があるものはまだそうしますが、CからAに移動するものは今からAからCに移動します。
結果は、サイクルのない依存関係グラフ(DAG)になるはずです。このプロパティをチェックしてグラフを描く varioustools があります。
より完全な説明については、 この記事 を参照してください:
依存関係逆転原理を正しく適用することの本質はこれです。
コード/サービス/…を、インターフェースと実装に依存させます。インターフェースは、それを使用するコードの専門用語で依存関係を再構築し、実装は、基礎となる手法の観点からそれを実装します。
実装はそのままです。しかし、インターフェースは現在、別の機能を持ち(別の専門用語/言語を使用しています)、コードを使用してできることを記述しています。それをそのパッケージに移動します。インターフェースと実装を同じパッケージに配置しないことにより、依存関係(の方向)がユーザー→実装から実装→ユーザーに反転します。