依存関係注入 (DI) は理解するのが簡単な概念ではなく、それを新規または既存のアプリケーションに適用することはさらに混乱を招きます。Jess Smith は、 C# および Java プログラミング言語で、注入コンテナーを使用せずに依存関係の注入を行う方法を示します。 この記事では、.NET および Java アプリケーションに依存関係挿入 (DI) を実装する方法を説明します。依存性注入の概念が初めて開発者の注目を集めたのは、Robert Martin が「設計の原則とパターン」(後に
SOLIDという頭字語で知られる) という記事を書いた 2000 年でした。SOLID の D は、Dependency of Inversion (DOI) を指します。これは、後に依存性注入として知られるようになりました。元の最も一般的な定義: 依存関係の反転は、基本クラスが依存関係を管理する方法を反転することです。
Copy
Martin の元の記事では、次のコードを使用して、クラスの下位レベルのクラスへの依存関係を説明しました
WritePrinter
。
void Copy()
{
int c;
while ((c = ReadKeyboard()) != EOF)
WritePrinter(c);
}
最初の明らかな問題は、メソッドのパラメータ リストまたは型を変更した場合
WritePrinter
、そのメソッドに依存関係がある場合は必ず更新を実装する必要があることです。このプロセスによりメンテナンス コストが増加し、新たなエラーが発生する可能性があります。
Java について興味がありますか? Java 開発者グループに参加してください。 |
もう 1 つの問題: Copy クラスは再利用の潜在的な候補ではなくなりました。たとえば、キーボードから入力された文字をプリンターではなくファイルに出力する必要がある場合はどうすればよいでしょうか。これを行うには、次のようにクラスを変更します
Copy
(C++ 言語構文)。
void Copy(outputDevice dev)
{
int c;
while ((c = ReadKeyboard()) != EOF)
if (dev == printer)
WritePrinter(c);
else
WriteDisk(c);
}
新しい依存関係が導入されたにもかかわらず
WriteDisk
、状況は改善されませんでした (むしろ悪化しました)。別の原則に違反したためです。「ソフトウェア エンティティ、つまりクラス、モジュール、関数などは拡張に対してオープンであるべきですが、拡張に対してはクローズされている必要があります」修正。" Martin 氏は、これらの新しい条件付き if/else ステートメントにより、コードの安定性と柔軟性が低下すると説明しています。解決策は、書き込みメソッドと読み取りメソッドが に依存するように依存関係を逆転することです
Copy
。依存関係を「ポップ」する代わりに、コンストラクターを介して渡されます。変更されたコードは次のようになります。
class Reader
{
public:
virtual int Read() = 0;
};
class Writer
{
public:
virtual void Write(char) = 0;
};
void Copy(Reader& r, Writer& w)
{
int c;
while((c=r.Read()) != EOF)
w.Write(c);
}
これで、クラス メソッドとのさまざまな実装で クラスを
Copy
簡単に再利用できるようになりました。このクラスには、型およびの内部構造に関する情報がないため、異なる実装でそれらを再利用できます。しかし、これがすべてある種の意味不明な話のように思われる場合は、おそらく次の Java と C# の例で状況が明確になるでしょう。
Reader
Writer
Copy
Reader
Writer
Java と C# の例
DI
依存関係コンテナーを使用しない依存関係の注入の容易さを説明するために、わずか数ステップで使用できるようにカスタマイズできる簡単な例から始めましょう。
HtmlUserPresentation
メソッドが呼び出されたときに HTML ユーザー インターフェイスを生成するクラスがあるとします。簡単な例を次に示します。
HtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
このクラス コードを使用するプロジェクトには class への依存関係があり
HtmlUserPresentation
、その結果、前述の使いやすさと保守性の問題が発生します。改善点はすぐに示唆されます。クラスで現在利用可能なすべてのメソッドのシグネチャを持つインターフェイスを作成することです
HtmlUserPresentation
。このインターフェイスの例を次に示します。
public interface IHtmlUserPresentation {
String createTable(ArrayList rowVals, String caption);
String createTableRow(String tableCol);
}
インターフェイスを作成した後、
HtmlUserPresentation
それを使用するようにクラスを変更します。type のインスタンス化に戻ると
HtmlUserPresentation
、基本型の代わりにインターフェイス型を使用できるようになります。
IHtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
インターフェイスを作成すると、 の他の実装を簡単に使用できるようになります
IHtmlUserPresentation
。たとえば、この型をテストしたい場合、基本型を
HtmlUserPresentation
という別の型に簡単に置き換えることができます
HtmlUserPresentationTest
。これまでに行われた変更により、コードのテスト、保守、拡張が容易になりますが、
HtmlUserPresentation
その型を使用するすべてのクラスがまだその存在を認識しているため、再利用することはできません。この直接的な依存関係を削除するには、インターフェイス型を、
IHtmlUserPresentation
それを使用するクラスまたはメソッドのコンストラクター (またはメソッド パラメーターのリスト) に渡すことができます。
public UploadFile(IHtmlUserPresentation htmlUserPresentation)
コンストラクターは
UploadFile
type のすべての機能にアクセスできるようになりました
IHtmlUserPresentation
が、このインターフェイスを実装するクラスの内部構造については何も知りません。このコンテキストでは、クラスのインスタンスが作成されるときに型インジェクションが発生します
UploadFile
。インターフェイス型は、
IHtmlUserPresentation
異なる機能を必要とする異なるクラスまたはメソッドに異なる実装を渡すことによって再利用可能になります。
資料を統合するための結論と推奨事項
依存関係の挿入について学習し、クラスの 1 つが別のクラスをインスタンス化してターゲット型の機能にアクセスする場合、クラスは相互に直接依存すると言うことを学びました。2 つのタイプ間の直接の依存関係を分離するには、インターフェイスを作成する必要があります。インターフェイスにより、必要な機能のコンテキストに応じて、型にさまざまな実装を含めることができます。インターフェイス型をクラス コンストラクターまたはメソッドに渡すことにより、その機能を必要とするクラス/メソッドは、インターフェイスを実装する型についての詳細を知りません。このため、インターフェイス タイプは、同様ではあるが同一ではない動作を必要とするさまざまなクラス間で再利用できます。
- 依存関係の注入を実験するには、1 つ以上のアプリケーションのコードを調べて、頻繁に使用される基本型をインターフェイスに変換してみてください。
- この基本型を直接インスタンス化するクラスを変更して、この新しいインターフェイス型を使用し、それを使用するクラス メソッドのコンストラクターまたはパラメーター リストを通じて渡します。
- このインターフェイス タイプをテストするためのテスト実装を作成します。コードがリファクタリングされると、
DI
実装が容易になり、再利用と保守性の点でアプリケーションの柔軟性が大幅に向上することに気づくでしょう。
GO TO FULL VERSION