9.1 依存関係の逆転
以前、サーバー アプリケーションでは単にストリームを作成することはできないと述べたことを覚えていますかnew Thread().start()
? コンテナのみがスレッドを作成する必要があります。今後はこのアイデアをさらに発展させていきます。
すべてのオブジェクトもコンテナによってのみ作成される必要があります。もちろん、すべてのオブジェクトについて話しているのではなく、いわゆるビジネス オブジェクトについて話しています。ビンとも呼ばれることがよくあります。このアプローチの脚部は、クラスを削除してインターフェイスに移行する必要がある SOLID の 5 番目の原則から発展しています。
- 最上位モジュールは下位レベルのモジュールに依存しないでください。これらもその他も抽象化に依存する必要があります。
- 抽象化は詳細に依存すべきではありません。実装は抽象化に依存する必要があります。
モジュールには特定の実装への参照が含まれていてはならず、モジュール間のすべての依存関係と相互作用は抽象化 (つまり、インターフェイス) にのみ基づいて構築される必要があります。このルールの本質は、一言で言えます。すべての依存関係はインターフェイスの形式でなければなりません。
その基本的な性質と見かけの単純さにもかかわらず、このルールはほとんどの場合違反されています。つまり、プログラム/モジュールのコード内で new 演算子を使用し、特定のタイプの新しいオブジェクトを作成するたびに、インターフェイスに依存するのではなく、実装への依存が形成されます。
これを避けることはできず、どこかにオブジェクトを作成する必要があることは明らかです。ただし、少なくとも、これが行われる場所やクラスが明示的に指定される場所の数を最小限に抑え、そのような場所をローカライズして分離して、プログラム コード全体に分散しないようにする必要があります。
非常に優れたソリューションは、ファクトリ、サービス ロケーター、IoC コンテナなどの特殊なオブジェクトとモジュール内で新しいオブジェクトの作成を集中させるというクレイジーなアイデアです。
ある意味、そのような決定は、「ソフトウェア システムが多くの代替をサポートする必要がある場合は常に、その完全なリストはシステムの 1 つのモジュールのみに知られるべきである」という単一選択の原則に従っています。
したがって、将来的に新しいオプション (または、検討している新しいオブジェクトの作成の場合のように新しい実装) を追加する必要がある場合は、この情報を含むモジュールのみを更新し、他のすべてのモジュールを更新するだけで十分です。影響を受けることはなく、通常どおり作業を続行できます。
例1
new ArrayList
のようなものを記述する代わりに、 List.new()
JDK がリーフの正しい実装 (ArrayList、LinkedList、さらには ConcurrentList) を提供する方が合理的です。
たとえば、コンパイラは、別のスレッドからオブジェクトへの呼び出しがあることを確認し、そこにスレッドセーフな実装を配置します。または、シートの中央に挿入が多すぎる場合、実装は LinkedList に基づいて行われます。
例 2
たとえば、これはすでにソートで起こっています。最後にコレクションを並べ替えるための並べ替えアルゴリズムを作成したのはいつですか? 代わりに、現在では誰もがメソッドを使用しておりCollections.sort()
、コレクションの要素は Comparable インターフェイス (comparable) をサポートする必要があります。
sort()
10 要素未満のコレクションをメソッドに渡す場合、クイックソートではなくバブル ソート (バブル ソート) を使用してコレクションをソートすることが十分に可能です。
例 3
コンパイラはすでに文字列の連結方法を監視しており、コードを に置き換えますStringBuilder.append()
。
9.2 実際の依存関係の逆転
ここで最も興味深いのは、理論と実践をどのように組み合わせることができるかを考えてみましょう。モジュールが「依存関係」を正しく作成および受信し、依存関係の反転に違反しないようにするにはどうすればよいでしょうか?
これを行うには、モジュールの設計時に次のことを自分で決定する必要があります。
- モジュールが何をするのか、どのような機能を実行するのか。
- 次に、モジュールがその環境から必要とするもの、つまり、どのようなオブジェクト/モジュールを処理する必要があるのかを決定します。
- そして彼はどうやってそれを手に入れるのでしょうか?
依存関係の反転の原則に準拠するには、モジュールがどの外部オブジェクトを使用するか、およびそれらの外部オブジェクトへの参照をどのように取得するかを決定する必要があります。
そして、次のオプションがあります。
- モジュール自体がオブジェクトを作成します。
- モジュールはコンテナからオブジェクトを取得します。
- モジュールはオブジェクトがどこから来たのか分かりません。
問題は、オブジェクトを作成するには特定の型のコンストラクターを呼び出す必要があり、その結果、モジュールがインターフェイスではなく特定の実装に依存することになります。ただし、モジュール コード内でオブジェクトを明示的に作成したくない場合は、ファクトリ メソッドパターンを使用できます。
「肝心なのは、new を介してオブジェクトを直接インスタンス化する代わりに、オブジェクトを作成するためのインターフェイスをクライアント クラスに提供するということです。このようなインターフェイスは常に適切な設計でオーバーライドできるため、低レベルのモジュールを使用するときにある程度の柔軟性が得られます」高レベルモジュール内」。
関連オブジェクトのグループまたはファミリーを作成する必要がある場合は、ファクトリ メソッドの代わりに抽象ファクトリが使用されます。
9.3 サービスロケーターの使用
モジュールは、必要なオブジェクトを既に持っているオブジェクトから取得します。システムにはオブジェクトの何らかのリポジトリがあり、モジュールはそのリポジトリにオブジェクトを「配置」したり、リポジトリからオブジェクトを「取得」したりできると想定されます。
このアプローチは Service Locator パターンによって実装されます。その主な考え方は、プログラムには、必要とされる可能性のあるすべての依存関係 (サービス) を取得する方法を知っているオブジェクトがあるということです。
ファクトリとの主な違いは、Service Locator はオブジェクトを作成しないが、実際にはインスタンス化されたオブジェクトがすでに含まれていることです (または、オブジェクトを取得する場所と方法がわかっており、作成する場合は最初の呼び出し時に 1 回だけ行う)。ファクトリは呼び出しごとに新しいオブジェクトを作成し、そのオブジェクトの完全な所有権を取得し、それに対して必要な操作を行うことができます。
重要!サービス ロケーターは、同じ既存のオブジェクトへの参照を生成します。したがって、Service Locator によって発行されたオブジェクトは、他の人が同時に使用できるため、細心の注意を払う必要があります。
Service Locator 内のオブジェクトは、構成ファイルを介して直接追加でき、プログラマにとって都合のよい方法で追加できます。Service Locator 自体は、一連の静的メソッド、シングルトン、またはインターフェイスを備えた静的クラスにすることができ、コンストラクターまたはメソッドを介して必要なクラスに渡すことができます。
Service Locator はアンチパターンと呼ばれることもあり、推奨されません (暗黙的な接続が作成され、優れた設計のように見えるだけであるため)。Mark Seaman の記事をさらに読むことができます。
9.4 依存性の注入
このモジュールは「マイニング」依存関係をまったく考慮しません。動作するために何が必要かを決定するだけであり、必要な依存関係はすべて他の誰かによって外部から提供 (導入) されます。
これは、依存関係の注入と呼ばれるものです。通常、必要な依存関係は、コンストラクター パラメーター (コンストラクター インジェクション) として、またはクラス メソッド (セッター インジェクション) を通じて渡されます。
このアプローチでは、依存関係を作成するプロセスが逆転します。依存関係の作成は、モジュール自体ではなく、外部から誰かによって制御されます。オブジェクトのアクティブなエミッターからのモジュールはパッシブになります。作成するのは彼ではなく、他の人が彼のために作成します。
この方向転換は、制御の反転、またはハリウッドの原則と呼ばれます - 「私たちに電話しないでください、私たちがあなたに電話します。」
これは最も柔軟なソリューションであり、モジュールに最大の自律性を与えます。「単一責任の原則」を完全に実装しているのはモジュールだけであると言えます。モジュールはその仕事をうまく遂行することに完全に集中し、他のことは何も気にしません。
作業に必要なものをすべてモジュールに提供することは別のタスクであり、適切な「専門家」が処理する必要があります (通常、特定のコンテナーである IoC コンテナーが依存関係とその実装の管理を担当します)。
実際、ここではすべてが日常生活と同じです。よく組織された会社では、プログラマーがプログラムを作成し、デスク、コンピューター、仕事に必要なものすべてがオフィスマネージャーによって購入され、提供されます。あるいは、プログラムのメタファーをコンストラクターとして使用する場合、モジュールはワイヤーについて考える必要はなく、パーツ自体ではなく、他の誰かがコンストラクターの組み立てに関与します。
モジュール間の依存関係を記述するためのインターフェイスの使用 (依存関係の反転) + これらの依存関係の適切な作成と注入 (主に依存関係の注入) が、デカップリングの重要なテクニックであると言っても過言ではありません。
これらは、コードの疎結合、柔軟性、変更への耐性、再利用の基盤として機能し、これがなければ他のすべてのテクニックはほとんど意味を持ちません。これは疎結合と優れたアーキテクチャの基礎です。
制御の反転の原理 (依存関係の注入およびサービス ロケーターと合わせて) については、Martin Fowler によって詳細に説明されています。彼の記事「Inversion of Control Containers and the dependency Injection pattern」と「Inversion of Control」の両方の翻訳があります。
GO TO FULL VERSION