SOLIDの理解:依存関係の反転

ウィキペディアの依存関係の逆転の原理の定義を見てみましょう。







依存関係反転の原則(DIP)は、コンピュータープログラムの接続性を減らすために使用されるオブジェクト指向プログラミングの重要な原則です。 上位5つの原則に含まれています。



言葉遣い:



A.上位レベルのモジュールは、下位レベルのモジュールに依存しないようにしてください。 どちらのタイプのモジュールも抽象化に依存する必要があります。

B.抽象化は詳細に依存するべきではありません。 詳細は抽象化に依存する必要があります。

私が話したほとんどの開発者は、定義の2番目の部分のみを理解しています。 「まあ、それで何が問題なのか、クラスを特定の実装ではなくインターフェースに結び付ける必要がある」 そして、それは本当のようですが、誰にだけインターフェースが属するべきですか? そして、なぜこの原則がそれほど重要なのでしょうか? 正しくしましょう。







モジュール



モジュールは、論理的に相互接続された機能要素のセットです。

誤解が何であれ、少し用語を紹介します。 モジュールとは、システムの機能的に関連する部分を意味します。 たとえば、フレームワークを独立した独立したモジュールとして配置し、別のユーザーと作業するロジックを配置できます。







モジュールは、システムの分解の要素にすぎません。 モジュールには、ツリーのようなものを形成する他のモジュールを含めることができます。 したがって、異なるレベルのモジュールを区別できます。







依存関係グラフ






ここで、モジュール間の矢印は、誰が何を使用するかを示しています。 したがって、これらの同じ矢印は、モジュール間の依存関係の方向を示します。







次に、「もう1つのボタン」を追加します。 そして、このボタンの機能はモジュールEに実装されていることを理解しています 必要なものを追加することをためらわず、モジュールとのやり取りのインターフェイスを変更する必要がありました。







私たちはすでにタスクを閉じてコードをコミットしたかったのですが、何かを変更しました...誰かを壊したかどうか見てみましょう。 ここで、変更のためにモジュールBが破損したことがわかりました。 わかった 修理済み。 モジュールBを使用している人が壊れた場合はどうなりますか? そして真実! モジュールAも落ちました。 修復...コミット、プッシュ。 さて、テストがある場合は、問題をすばやく見つけて、すぐに修正できます。 しかし、それに直面してみましょう、テストを書く人はほとんどいません。







そして、テスターからのバグがあなたの同僚に飛びました、彼らはモジュールCが壊れたと言います。 彼はあなたのモジュールEにうっかり縛り付けられたが、それについてあなたに話さなかったことが判明した。 さらに、このモジュールは多数のファイルで構成されており、モジュールEの全員が何かを必要とします。 そして今、彼はまた、彼の部分の依存関係グラフを登ります(システムのあなたの部分ではなく、あなたよりもナビゲートする方が簡単だからです)。







依存関係グラフの変更






上の図で、オレンジ色の円は修正したいモジュールを示しています。 そして、修正する必要があった赤いもの。 そして、各円が1つのクラスであるという事実ではありません。 コンポーネント全体でもかまいません。 そして、多くのモジュールがなく、それらがあまり重ならないのは良いことです。 しかし、各円をそれぞれに接続するとどうなりますか? これは、くしゃみのすべてを修正することです。 そして最後に、ボタンを追加するという単純なタスクは、システムの一部をリファクタリングすることに変わります。 になる方法







インターフェイスと遅延バインディング



遅延バインディングとは、コンパイル時ではなく、プログラムの実行中にのみオブジェクトが関数呼び出しに関連付けられることを意味します。

ご存知のように、インターフェイスは特定のコントラクトを定義します。 そして、この契約を実装するすべてのオブジェクトは、それに準拠する必要があります。 たとえば、ユーザー登録を作成します。 また、要件を覚えておいてください-データベースからデータが漏洩した場合、ユーザーのパスワードは安全にハッシュされなければなりません。 現時点でそれを正しく行う方法がわからないとします。 そして、プロジェクトを実行するためのフレームワークまたはライブラリをまだ選択していないとします。 気違い、私は知っている...しかし、今私たちはアプリケーションのロジック以外何も持っていないことを想像してみましょう。







私たちは需要を思い出しますが、私たち全員を投げませんか? 最初にユーザー登録を完了し、それからどうするかを理解しましょう。 それにもかかわらず、仕事に対する一貫したアプローチが必要です。 したがって、「パスワードを正しくハッシュする方法」をグーグルで調べたり、フレームワークでそれを行う方法を理解する代わりに、 PasswordEncoder



インターフェースを作成しましょう。 これを行った後、「契約」を作成します。 このインターフェイスを実装することを決定した人は、信頼できる安全なパスワードハッシュを提供する必要があります。 インターフェイス自体はめちゃくちゃシンプルです:







 interface PasswordEncoder { public function encode(string $password): string; }
      
      





これは、特定の時間に作業するために必要なものです。 私たちはこれがどのように起こるか知りたくありません、私たちはまだ塩と遅いハッシュについて知りません。 開発時に詰め込んだものを返すスタブを作成できます。 そして、通常の実装を行います。 同様に、ユーザーの登録に成功したという事実に関するメールを送信することもできます。 これらのインターフェースを実装してくれる人をもっと多く植えることもできます。 美人







そして、その美しさは、実装を動的に置き換えることができることです。 つまり、ユーザー登録を呼び出す直前に、使用する必要があるパスワードエンコーダーを選択します。 これが遅延バインディングの意味です。 使用する直前に実装を「選択」する機能。







PHPなどの動的型システムを使用する言語では、レイトバインディングを実現するさらに簡単な方法があります。型ヒントを使用しないでください。 すべての言葉から。 確かに、これを行うと、誰が何を使用するかに関する静的(コードで明示的に提示)情報が完全に失われます。 そして、何かを変更するとき、コードが壊れているかどうかを判断するのはそれほど簡単ではありません。 これはライトをオフにして、左と右の99の山で靴下を探す方法です。







依存関係の反転



そのため、モジュールEがすべてを壊すことをすでに決定しています。 そして、あなたの同僚は、「エイリアン」コードの将来の変更から身を守りたいと思っていました。 結局のところ、このモジュールの1つの関数のみを使用します。







これを行うために、彼はCモジュールにインターフェースを作成し、目的のモジュールから依存関係を取得し、目的のメソッドへのアクセスのみを提供する簡単なアダプターを作成しました。 これで、何かを修正すると、「故障」を1か所で修正できるようになります。







さらに、アダプターがモジュールEの境界にある場合、このインターフェースはモジュールCの境界にあります 同様に、モジュールEの開発者がコードを修正するためにモジュールEを頭に入れた場合、アダプターを修正する必要があります。







さて、すぐにこのモジュールを完全に書き換え、依存モジュールも保護する必要があると判断しました。 モジュールEからより多くを使用するため、同僚のインターフェイスは私たちには適していません。 私たちは自分自身を実現する必要があります。 また、このインターフェイスをモジュールEのフレームワークに実装する必要があります。そのため、後で書き換えるときに、実装を修正することを忘れないでください。 何が起こったのか見てみましょう:







反転依存グラフ






1つではなく2つのインターフェイスがあることが非常に重要です。 インターフェイスをモジュールEに配置した場合、モジュール間の依存関係は排除されません。 さらに、異なるモジュールには異なる機能が必要です。 私たちのタスクは、使用する部分を正確に分離することです。 これにより、サポートが大幅に簡素化されます。







また、上の図を見ると、アダプターの実装はモジュールEにあるため 、このモジュールは他のモジュールからのインターフェースの実装を強制されることに気付くことができます。 したがって、依存関係を示す矢印の方向を反転しました。 依存関係反転しました







すべての依存関係が反転する価値があるわけではありません。



モジュールの相互接続が少なくなり、実際に達成されました。 他のモジュールの変更は今後数年間は予想されないため、すべてに対してこれを行ったわけではありません。 めったに変更されないものの変更について心配する必要はありません。 しかし、頻繁に変更されるシステムの部分がある場合、または最終的に何が起こるかわからない場合は、起こりうる変更から身を守るのが理にかなっています。







たとえば、ロガーが必要な場合は、 PSR\Logger



インターフェイスが標準化されているため、常に使用できます。このようなことはほとんどありません。 その後、好みに応じてこのインターフェイスを実装するロガーを選択できます。







コンポーネント間の依存関係の反転






ご覧のとおり、このインターフェイスのおかげで、アプリケーションはまだ特定のロガーに依存していません。 ロガーはこの抽象化に依存しています。 ただし、両方の「モジュール」は互いに独立しています。







分離



インターフェイスと遅延バインディングにより、外部の詳細からロジックの実装を「抽象化」できます。 モジュールをできる限り隔離され、自給自足できるようにする必要があります。 すべてのモジュールが独立している場合、それらを独立して開発する機会があります。 また、これはビジネスの観点からも重要です。







多くの場合、抽象化に関しては、人々はすべてを極端なものにしたがり、それが本来必要だった理由を忘れてしまいます。







プロジェクトがフレームワークのサポート期間よりもはるかに長い期間サポートされる予定の場合、使用済みのものをすべてアダプターにラップすることは理にかなっています。 これは一種の極端ですが、そのような状況では正当化されます。 フレームワークを変更することはほとんどありませんが、将来的にメジャーバージョンを痛みなく更新したいと考えています。







または、たとえば、別の一般的な誤解は、リポジトリからの抽象化です。 データベースを完全に置き換える機能は、この抽象化を実装する目的ではなく、品質基準です。 代わりに、このレベルの分離を行うだけで、ロジックがデータベースの機能に依存しないようにできます。 そして、これは、これらの機会を利用すべきではないという意味ではありません。







たとえば、お気に入りのMySQLで検索を実装しましたが、最終的にはより良い実装が必要でした。 また、検索が高速であるという理由だけで、ElasticSearchを使用することにしました。 MySQLを拒否することもできませんが、組み込みの抽象化のおかげで、特定のタスクをより効率的に実行するためにデータベースをもう1つ追加できます。







または、別のソーシャルネットワークを使用しており、何らかの形で再投稿を追跡する必要があります。 はい、MySQLで実行できますが、不便です。 ここでグラフデータベースは請います。 そして、そのようなシナリオはたくさんあります。 教義ではなく、そもそも常識に導かれるべきです。







おそらくそれだけです。 私はすべてを言わなかったと確信し、質問があるかもしれませんので、コメントでそれらを尋ねることをheしないでください。 また、すべてをまったく知らないことも確信しており、依存関係の逆転が助けた、または助けられる可能性があるときに、トピックについてもう少し詳しくコメントしたり、人生の例について喜んでコメントしたりします。 まあ、もしあなたが記事にタイプミス/エラーを見つけたら-PMのメッセージに喜んでいるでしょう。








All Articles