NInjectを使用して工場を建設する最良の方法は何ですか?


27

私は、MVC3でNInjectを使用した依存性注入にかなり満足しています。MVC3アプリケーションでの作業中に、私はNInjectを使用してカスタムコントローラー作成ファクトリを開発しました。そのため、作成されたコントローラーには、このコントローラーファクトリを介して依存関係が注入されます。

今、私はWindowsアプリケーションを開発し始めています、私はアプリケーション全体の依存性注入を使用したいと思います。つまり、ユニットテストを容易にするために、すべてのオブジェクトをNInjectを介して作成する必要があります。作成されたすべてのオブジェクトがNInject Factoryのみを通過するようにする必要があります。

たとえば、Button_Clickイベントで任意のWindowsフォームで次のように記述した場合:

TestClass testClass = new TestClass()

そしてTestClass、たとえば、上の任意の依存関係を持ってITest、それが自動的に解決されなければなりません。私が使用できることを知っています:

Ikernel kernel = new StandardKenel()
//AddBinding()
TestClass testClass = kenel.get<TestClass>();

しかし、オブジェクトを作成するたびにこれを行うのは面倒です。また、開発者は特定の方法でオブジェクトを作成する必要があります。もっと良くできますか?

オブジェクト作成用の中央リポジトリを作成すると、すべてのオブジェクト作成でそのリポジトリが自動的に使用されますか?


1
こんにちはPravin Patil:すばらしい質問です。あなたが何を求めているのかを明確にするために、タイトルを少し変更しました。マークを逃した場合は自由に変更してください。

@MarkTrapp:適切なタイトルをありがとう。私は...そのキャッチフレーズを逃した
Pravinパティル

副次的な注意事項として、プロジェクトのスペルは「NInject」ではなく「Ninject」です。En-Injectだったのかもしれませんが、最近はニンジャをテーマにプレイしています。:)Cf。ninject.org-
コーネリアス

回答:


12

クライアントアプリケーションの場合、多くの場合、MVP(またはMVVM)などのパターンを適応させ、フォームから基になるViewModelまたはPresenterへのデータバインディングを使用するのが最善です。

ViewModelの場合、標準のConstructor Injectionを使用して必要な依存関係を注入できます。

アプリケーションのコンポジションルートでは、アプリケーションのオブジェクトグラフ全体を結び付けることができます。このためにDIコンテナ(Ninjectなど)を使用する必要はありませんが、可能です。


7

通常、Windowsフォームアプリケーションには、次のようなエントリポイントがあります。

    // Program.cs
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }

コード内のこのポイントをコンポジションのルートにすると、NinjectをService Locatorであるかのように明示的に呼び出すコードがある場所の数を大幅に減らすことができます。

    // Program.cs
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        var kernel = InitializeNinjectKernel();
        Application.Run(kernel.Get<MainForm>());
    }

この時点から、コンストラクター注入を介してすべての依存関係を注入します。

public MainForm(TestClass testClass) {
    _testClass = testClass;
}

「依存」が複数回生成できる必要がある場合、本当に必要なのはファクトリーです。

public MainForm(IFactory<TestClass> testClassFactory) {
    _testClassFactory = testClassFactory;
}

...
var testClass = _testClassFactory.Get();

この方法でIFactoryインターフェイスを実装すると、大量の1回限りの実装を作成する必要がなくなります。

public class InjectionFactory<T> : IFactory<T>, IObjectFactory<T>, IDependencyInjector<T>
{
    private readonly IKernel _kernel;
    private readonly IParameter[] _contextParameters;

    public InjectionFactory(IContext injectionContext)
    {
        _contextParameters = injectionContext.Parameters
            .Where(p => p.ShouldInherit).ToArray();
        _kernel = injectionContext.Kernel;
    }

    public T Get()
    {
        try
        {
            return _kernel.Get<T>(_contextParameters.ToArray());
        }
        catch (Exception e)
        {
            throw new Exception(
                string.Format("An error occurred while attempting to instantiate an object of type <{0}>",
                typeof(T)));
        }
    }

...
Bind(typeof (IFactory<>)).To(typeof (InjectionFactory<>));
Bind(typeof (IContext)).ToMethod(c => c.Request.ParentContext);

このファクトリの完全な実装はありますか?
テボ

@ColourBlend:いいえ。ただし、InjectionFactory<T>実装していた他のインターフェイスを取り除くと、提供されたコードは問題なく動作するはずです。特に何か問題がありますか?
StriplingWarrior

すでに実装しました。クラス内に他の興味深いものがあったかどうかを知りたいだけです。
テボ

@Tebo:渡すことができるファクトリーなど、他のいくつかのDI関連インターフェースTypeType実装しましたが、そのために水和したオブジェクトが特定のジェネリック型を実装または拡張することを保証します。特別なことは何もありません。
StriplingWarrior

4

IoCコンテナーのアダプターラッパーは常に次のように記述します。

public static class Ioc
{
    public static IIocContainer Container { get; set; }
}

public interface IIocContainer 
{
    object Get(Type type);
    T Get<T>();
    T Get<T>(string name, string value);
    void Inject(object item);
    T TryGet<T>();
}

Ninjectの場合、具体的には、具象アダプタクラスは次のようになります。

public class NinjectIocContainer : IIocContainer
{
    public readonly IKernel Kernel;
    public NinjectIocContainer(params INinjectModule[] modules) 
    {
        Kernel = new StandardKernel(modules);
        new AutoWirePropertyHeuristic(Kernel);
    }

    private NinjectIocContainer()
    {
        Kernel = new StandardKernel();
        Kernel.Load(AppDomain.CurrentDomain.GetAssemblies());

        new AutoWirePropertyHeuristic(Kernel);
    }

    public object Get(Type type)
    {
        try
        {
            return Kernel.Get(type);
        }
        catch (ActivationException exception)
        {
            throw new TypeNotResolvedException(exception);
        }              
    }

    public T TryGet<T>()
    {
        return Kernel.TryGet<T>();
    }

    public T Get<T>()
    {
        try
        {
            return Kernel.Get<T>();
        }
        catch (ActivationException exception)
        {
            throw new TypeNotResolvedException(exception);
        }           
    }

    public T Get<T>(string name, string value)
    {
        var result = Kernel.TryGet<T>(metadata => metadata.Has(name) &&
                     (string.Equals(metadata.Get<string>(name), value,
                                    StringComparison.InvariantCultureIgnoreCase)));

        if (Equals(result, default(T))) throw new TypeNotResolvedException(null);
            return result;
    }

    public void Inject(object item)
    {
        Kernel.Inject(item);
    }
}

これを行う主な理由は、IoCフレームワークを抽象化することです。そのため、いつでもフレームワークの違いを置き換えることができます。

ただし、おまけとして、IoCフレームワークを、それを本来サポートしていない他のフレームワーク内で使用することで、物事がはるかに簡単になります。たとえば、WinFormsの場合、2つのステップがあります。

Mainメソッドで、他の操作を行う前にコンテナをインスタンス化します。

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        try
        {
            Ioc.Container = new NinjectIocContainer( /* include modules here */ );
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MyStartupForm());
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }
}

そして、他のフォームが派生し、それ自体でInjectを呼び出すベースフォームがあります。

public IocForm : Form
{
    public IocForm() : base()
    {
        Ioc.Container.Inject(this);
    }
}

これは、自動配線ヒューリスティックに、モジュールに設定されたルールに適合するフォームのすべてのプロパティを再帰的に挿入しようとすることを伝えます。


非常に素晴らしい解決策.....それを試してみます。
プラビンパティル

10
それはサービスロケーターであり、これは非常に悪い考えです:blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
マーク

2
@MarkSeemann:サービスロケーターは、どこからでもアクセスできる場合、トップレベルのオブジェクトをできる限り下に接続するのではなく、悪い考えです。ページの少し下にあるMark自身のコメントを読んでください:「そのような場合、コンポジションルートを各オブジェクト(ページなど)に移動する以外に手段はありません。DIコンテナに依存関係をそこから接続させます。 Service Locatorアンチパターンがありますが、それはコンテナの使用量を最小限に抑えるためではありません。」(編集:待って、あなたはマークです!だから違いは何ですか?)
pdr

1
違いは、Singleton Service Locatorをどのクラスでも使用できるようにする代わりに、Composerからコードベースの残りを保護できることです。
マークシーマン

2
@pdr:私の経験では、属性クラスのようなものにサービスを注入しようとすると、懸念を適切に分離できません。あなたが使用しているフレームワークが適切な依存性注入を使用することを事実上不可能にする場合があり、時にはサービスロケーターを使用することを余儀なくされますが、私はこれに戻す前に可能な限り真のDIを確実に取得しようとしますパターン。
StriplingWarrior

1

依存性注入の適切な使用は、通常、オブジェクトと実際のビジネスロジックを作成するコードを分離することに依存しています。言い換えれば、私は自分のチームがそのようにクラスのインスタンスを頻繁に使用して作成することを望まないでしょうnew。それが完了すると、作成した型を別の型に簡単に交換する方法はありません。具体的な型を既に指定しているからです。

そのため、これを修正するには2つの方法があります。

  1. クラスに必要なインスタンスを注入します。この例では、TestClassWindowsフォームにa を挿入して、必要なときにインスタンスが既にあるようにします。Ninjectがフォームをインスタンス化すると、自動的に依存関係が作成されます。
  2. 本当に必要になるまでインスタンスを作成したくない場合は、ビジネスロジックにファクトリを挿入できます。たとえばIKernel、Windowsフォームにを挿入し、それを使用してをインスタンス化できますTestClass。スタイルに応じて、これを実現する他の方法(ファクトリクラス、ファクトリデリゲートなど)があります。

これにより、テストクラスを使用するコードを実際に変更することなく、具体的なタイプのTestClassを簡単に交換したり、テストクラスの実際の構造を変更したりできます。


1

私はNinjectを使用していませんが、IoCを使用している場合の標準的な作成方法は、作成するオブジェクトのタイプであるFunc<T>where Tを使用して作成することです。したがって、オブジェクトT1がタイプのオブジェクトを作成する必要がある場合T2、のコンストラクターにT1はタイプのパラメーターが必要であり、このパラメーターはFunc<T1>のフィールド/プロパティとして保存されますT2。ここで、のタイプのオブジェクトを作成するときにT2T1を呼び出しますFunc

これはあなたをIoCフレームワークから完全に切り離し、IoCの考え方でコーディングする正しい方法です。

これを行うFuncことのデメリットは、手動でs を配線する必要がある場合や作成者がパラメーターを必要とする場合に煩わしくなり、IoCが自動配線できないFuncことです。

http://code.google.com/p/autofac/wiki/RelationshipTypes

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.