151

オブジェクトの作成を 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 回限りの初期化パラメーターが必要であるという知識です。この情報を使用してインターフェイスに注釈を付け(属性?)、オブジェクトの作成時にそれらをフレームワークに渡す方法はありますか?

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

4

5 に答える 5

283

特定の依存関係を構築するために実行時の値が必要な場所では、Abstract Factoryがソリューションです。

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

あなたの場合、実装を作成する方法ではなく、使用する必要がある方法IMyIntfでインターフェイスをモデル化する必要があると思います。それは実装の詳細です。

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

public interface IMyIntf
{
    string RunTimeParam { get; }
}

次に、Abstract Factory を定義します。

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臭い Initialize メソッドは必要ありません。

実装は次のIMyIntfFactoryように単純です。

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

インスタンスが必要なすべてのコンシューマーで、コンストラクター インジェクションを介してリクエストすることIMyIntfで依存関係を取得します。IMyIntfFactory

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

于 2009-12-22T08:50:09.043 に答える
16

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

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

セッターまたは Initialize() メソッドを使用することは、一般的に悪い設計であると考えられています。それらを呼び出すことを「覚える」必要があり、それらが実装の状態をあまり開かないようにする必要があるためです (つまり、誰かが re -initialize または setter を呼び出していますか?)。

于 2009-12-22T03:03:57.933 に答える
5

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

インターフェイスに基づいてファクトリを動的に作成できるNinject 拡張機能が気に入りました。

Bind<IMyFactory>().ToFactory();

Unityで直接同様の機能を見つけることができませんでした。そこで、 IUnityContainerに独自の拡張機能を作成しました。これにより、既存のオブジェクトのデータに基づいて新しいオブジェクトを作成するファクトリを登録できます。基本的に、ある型階層から別の型階層にマッピングされます: UnityMappingFactory@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 のプロジェクトのサイトには、さらに多くのドキュメントがあります。

于 2013-07-11T19:10:44.107 に答える
1

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

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

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

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

于 2009-12-22T00:56:35.897 に答える
1

私はそれを解決したと思いますし、それはむしろ健全に感じられるので、半分正しいに違いありません:))

IMyIntf「ゲッター」インターフェースと「セッター」インターフェースに分割します。そう:

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 パラダイムのビットを使用して非常にうまくまとめることができるためIMyIntfSetterIMyIntf.

于 2009-12-22T02:08:31.867 に答える