web-dev-qa-db-ja.com

DIコンテナを介して作成されたオブジェクトを初期化するためのパターンはありますか

Unityにオブジェクトの作成を管理させようとしていますが、実行時まで不明な初期化パラメーターが必要です。

現時点では、その方法を考えることができる唯一の方法は、インターフェイスにInitメソッドを設定することです。

interface IMyIntf {
  void Initialize(string runTimeParam);
  string RunTimeParam { get; }
}

それから(Unityで)それを使用するには、これを行います:

var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");

このシナリオでは、runTimeParam paramはユーザー入力に基づいて実行時に決定されます。ここでの単純なケースは、単にrunTimeParamの値を返しますが、実際にはパラメーターはファイル名のようなものになり、initializeメソッドはファイルに対して何かを行います。

これにより、多くの問題が発生します。つまり、Initializeメソッドがインターフェイスで使用可能であり、複数回呼び出すことができます。実装にフラグを設定し、Initializeを繰り返し呼び出したときに例外をスローすると、かなり不格好に思えます。

インターフェースを解決する時点では、IMyIntfの実装について何も知りたくありません。しかし、私が望んでいるのは、このインターフェイスが特定の1回限りの初期化パラメーターを必要とするという知識です。この情報でインターフェースに何らかの方法で注釈を付け(属性?)、オブジェクトの作成時にそれらをフレームワークに渡す方法はありますか?

編集:インターフェースについてもう少し説明しました。

145
Igor Zevaka

特定の依存関係を構築するためにランタイム値が必要な場所、Abstract Factoryが解決策です。

インターフェイスでInitializeメソッドを使用すると、Leaky Abstractionの匂いがします。

あなたの場合、私はあなたがどのようにそれを使用する必要があるかIMyIntfインターフェースをモデル化するべきだと言うでしょうその実装。それは実装の詳細です。

したがって、インターフェースは単純に次のようになります。

public interface IMyIntf
{
    string RunTimeParam { get; }
}

次に、抽象ファクトリを定義します。

public interface IMyIntfFactory
{
    IMyIntf Create(string runTimeParam);
}

次のようなIMyIntfFactoryの具象インスタンスを作成するIMyIntfの具象実装を作成できるようになりました。

public class MyIntf : IMyIntf
{
    private readonly string runTimeParam;

    public MyIntf(string runTimeParam)
    {
        if(runTimeParam == null)
        {
            throw new ArgumentNullException("runTimeParam");
        }

        this.runTimeParam = runTimeParam;
    }

    public string RunTimeParam
    {
        get { return this.runTimeParam; }
    }
}

これにより、readonlyキーワードを使用してクラスの不変式を保護するが可能になることに注意してください。臭い初期化メソッドは必要ありません。

IMyIntfFactory実装は、次のように簡単な場合があります。

public class MyIntfFactory : IMyIntfFactory
{
    public IMyIntf Create(string runTimeParam)
    {
        return new MyIntf(runTimeParam);
    }
}

IMyIntfインスタンスが必要なすべてのコンシューマーで、Constructor Injectionを介して要求することにより、IMyIntfFactoryに依存関係を単純に取得します。

ソルトに値するDIコンテナは、正しく登録するとIMyIntfFactoryインスタンスを自動配線できます。

268
Mark Seemann

通常、このような状況に遭遇した場合、設計を再検討し、ステートフル/データオブジェクトを純粋なサービスと混合しているかどうかを判断する必要があります。ほとんどの場合(すべてではありません)、これらの2種類のオブジェクトを別々にしたいでしょう。

コンストラクターにコンテキスト固有のパラメーターを渡す必要がある場合、1つのオプションは、コンストラクターを介してサービスの依存関係を解決するファクトリーを作成し、ランタイムパラメーターをCreate()メソッド(またはGenerate( )、Build()、またはファクトリメソッドに名前を付けます)。

セッターまたはInitialize()メソッドを持つことは一般的に設計が悪いと考えられます。それらを呼び出すことを「覚えて」、実装の状態を過度に開かないようにする必要があるためです誰かが初期化またはセッターを再呼び出しするのを止めることは何ですか?)。

14
Phil Sandler

また、モデルオブジェクトに基づいてViewModelオブジェクトを動的に作成している環境(この他の Stackoverflow post で非常によく概説されている)でも、この状況に何度か遭遇しました。

Ninject extension を使用すると、インターフェイスに基づいて動的にファクトリを作成できます。

Bind<IMyFactory>().ToFactory();

Unity;に同様の機能を直接見つけることができませんでした。したがって、IUnityContainerに独自の拡張機能を作成しました。これにより、既存のオブジェクトのデータに基づいて新しいオブジェクトを作成するファクトリを登録できます。別のタイプ階層に: nityMappingFactory @ GitHub

シンプルさと読みやすさを目標に、個々のファクトリクラスまたはインターフェイスを宣言せずにマッピングを直接指定できる拡張機能になりました(リアルタイムセーバー)。通常のブートストラップ処理中にクラスを登録する場所にマッピングを追加するだけです...

//make sure to register the output...
container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>();
container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>();

//define the mapping between different class hierarchies...
container.RegisterFactory<IWidget, IWidgetViewModel>()
.AddMap<IImageWidget, IImageWidgetViewModel>()
.AddMap<ITextWidget, ITextWidgetViewModel>();

次に、CIのコンストラクターでマッピングファクトリインターフェイスを宣言し、そのCreate()メソッドを使用します...

public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { }

public TextWidgetViewModel(ITextWidget widget) { }

public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory)
{
    IList<IWidgetViewModel> children = new List<IWidgetViewModel>();
    foreach (IWidget w in data.Widgets)
        children.Add(factory.Create(w));
}

追加のボーナスとして、マップされたクラスのコンストラクター内の追加の依存関係も、オブジェクトの作成中に解決されます。

明らかに、これはすべての問題を解決するわけではありませんが、これまでのところ私に役立っているので、私はそれを共有すべきだと思いました。 GitHubのプロジェクトのサイトにはさらにドキュメントがあります。

5
jigamiller

私はそれを解決したと思うが、それはかなり健全だと思うので、それは半分正しいはずです:))

IMyIntfを「getter」インターフェースと「setter」インターフェースに分割しました。そう:

_interface IMyIntf {
  string RunTimeParam { get; }
}


interface IMyIntfSetter {
  void Initialize(string runTimeParam);
  IMyIntf MyIntf {get; }
}
_

次に、実装:

_class MyIntfImpl : IMyIntf, IMyIntfSetter {
  string _runTimeParam;

  void Initialize(string runTimeParam) {
    _runTimeParam = runTimeParam;
  }

  string RunTimeParam { get; }

  IMyIntf MyIntf {get {return this;} }
}

//Unity configuration:
//Only the setter is mapped to the implementation.
container.RegisterType<IMyIntfSetter, MyIntfImpl>();
//To retrieve an instance of IMyIntf:
//1. create the setter
IMyIntfSetter setter = container.Resolve<IMyIntfSetter>();
//2. Init it
setter.Initialize("someparam");
//3. Use the IMyIntf accessor
IMyIntf intf = setter.MyIntf;
_

IMyIntfSetter.Initialize()は引き続き複数回呼び出すことができますが、 Service Locator paradigm のビットを使用して、IMyIntfSetterがほぼ別個の内部インターフェイスになるように非常にうまくまとめることができますIMyIntfから。

1
Igor Zevaka

特定のUnityの用語で答えることはできませんが、依存関係の注入について学習しているようです。もしそうなら、私はあなたに簡潔で、明確で、情報が詰まっていることを読むことをお勧めします Ninjectのユーザーガイド

これにより、DIを使用する際のさまざまなオプションと、その過程で直面する特定の問題を説明する方法を説明します。あなたの場合、DIコンテナを使用してオブジェクトをインスタンス化し、そのオブジェクトにコンストラクタを介して各依存関係への参照を取得させたいと思うでしょう。

また、このチュートリアルでは、実行時に属性を使用してメソッド、プロパティ、さらにはパラメーターに注釈を付ける方法について詳しく説明します。

Ninjectを使用しない場合でも、ウォークスルーは目的に合った機能の概念と用語を提供し、その知識をUnityまたは他のDIフレームワークにマッピングできるはずです(またはNinjectに試してもらうよう説得します) 。

1
anthony