これは、DI を使い始めるときに理解するのが非常に難しいことであり、簡単に説明することもできません。
メソッドを介して後で初期化される「空の」オブジェクトを作成することは、最適ではない解決策である可能性があるというあなたの考えは正しいです-オブジェクトはいつでもその作業を行うことができるはずです。Initialize()
メソッドは、Mark Seemann が著書Dependency Injection in .NETで「時間結合」と呼んでいるものです。これは、オブジェクトを使用するコードをそのオブジェクトの内部動作に依存させ、カプセル化を破るアンチパターンです。
問題は、必要な情報がいつ利用可能になるか、「それを初期化する責任のあるコード」とは何か、情報をどこから取得するか、そしてオブジェクトを初期化するためにオブジェクトにアクセスする方法です。理想的には、この初期化コード自体がオブジェクトに注入され、オブジェクトのメソッド/プロパティがアクセスされるたびに、他の依存関係からの初期化を要求します。
また、IsInitialized
フラグが false を返すとどうなりますか? それはまだ有効なプログラム状態ですか?
一般に、依存性が注入されたオブジェクト グラフ内のオブジェクトとして、作成時に自分のすべての「構成」データを知っているか、それを提供してくれる人 (依存性として注入された別のオブジェクトであること) を知っている必要があります。
オブジェクトが必要とするパラメーターの種類と、それらがどこから取得される必要があるかについて、もう少し詳しく説明していただけると助かります。
編集
あなたがコメントで説明していることは、この種の問題との最初の出会いです。私が当時投稿したSOのどこかに質問があります。
重要なことは、個々のクラスを構築することです (通常、例外はあるかもしれませんが、それが何であるかは経験の問題です)。クラスに必要なものがすべて揃っていると想定できるようにします。プログラムが実行されているとき、その仮定が失敗しないことを確認する他のクラスが必要です。
セッター注入は、私が通常、上記の一時的な結合を回避する必要がないように努めるものです。Mark Seemann によると、通常、setter インジェクションは、setter を介して上書きするだけの適切なデフォルトが既にある場合にのみ使用する必要があります。ただし、この場合、オブジェクトはその依存関係がなければ適切に機能できません。
これはこれを行うための最もエレガントな方法ではないかもしれません (私は通常、かなり閉じたコードのみの環境で、UI を気にせずに DI を適用する余裕があります)、それは機能します (一種の - コンパイルされますが、それでも擬似コード):
public class MainForm
{
private readonly IDataManager _dataManager;
private readonly IConnectionProvider _connectionProvider;
private readonly IConnectionReceiver _connectionReceiver;
public MainForm(IDataManager dataManager, IConnectionProvider connectionProvider, IConnectionReceiver connectionReceiver)
{
this._dataManager = dataManager;
this._connectionProvider = connectionProvider;
this._connectionReceiver = connectionReceiver;
}
public void btnConnect_Click()
{
IConnection connection = this._connectionProvider.GetConnection();
if (null != connection)
{
this._connectionReceiver.SetConnection(connection);
this.SetFormControlsEnabled(true);
}
}
private void SetFormControlsEnabled(bool doEnable)
{
}
}
public interface IConnectionProvider
{
IConnection GetConnection();
}
public interface IConnectionReceiver
{
void SetConnection(IConnection connection);
}
public interface IConnection
{
IConnectionWebService ConnectionWebService { get; }
}
public class ConnectionBridge : IConnection, IConnectionReceiver
{
private IConnection _connection;
#region IConnectionReceiver Members
public void SetConnection(IConnection connection)
{
this._connection = connection;
}
#endregion IConnectionReceiver Members
#region IConnection Members
public IConnectionWebService ConnectionWebService
{
get { return this._connection.ConnectionWebService; }
}
#endregion
}
public interface IConnectionWebService {}
public interface IDataManager { }
public class DataManager : IDataManager
{
public DataManager(IConnection connection)
{
}
}
それで、MainForm
それをすべて一緒に保持するものです。コントロールが無効になっている状態で開始します。これは、それらが機能する必要があり、IDataManager
(慣例により) 接続が必要であることを認識しているためです。「接続」ボタンをクリックすると、フォームはそのIConnectionProvider
依存関係に接続を要求します。その接続がどこから来るかは気にしません。接続プロバイダーは、別のフォームを表示して資格情報を要求したり、ファイルから資格情報を読み取ったりする場合があります。
次に、フォームは接続をIConnectionReceiver
インスタンスに渡す必要があることを認識し、その後、すべてのコントロールを有効にできます。これは DI の原則によるものではありませんMainForm
。
一方、データ マネージャーには、最初から必要なものすべて (IConnection
インスタンス) があります。最初は本来の動作を実行できませんが、問題の発生を防止する別のコードがあります。
ConnectionBridge
実際のインスタンスのデコレータであり、IConnection
接続の消費から接続の取得を切り離すアダプタでもあります。これは、 Interface Segregation Principleを採用することによって行われます。
余談ですが、依存性注入は重要な手法ですが、「クリーン コード」と呼ばれるものを作成するために従うべきいくつかの原則の 1 つにすぎないことに注意してください。最もよく知られているのはSOLIDの原則 (DI はその 1 つ) ですが、他にもCommand-Query-Separation (CQS)、"Don't repeat yourself" (DRY)、Demeter の法則などがあります。その上で、単体テスト、正確にはテスト駆動開発 (TDD)を練習します。これらのことは本当に大きな違いをもたらします - しかし、あなたが自発的にDIを始めているなら、あなたはすでに良いスタートを切っています.