1

NuGetのNinject3.0.1.10とninject.extensions.factory3.0.1.0を使用しています-「実際の」シナリオでは、(手動でIFooをバインドするのではなく)ninject.extensions.conventionsも使用しますが、質問を単純化するために、これを避けてください。

私はIFooインターフェースとその複数の実装を持っており、それぞれがGen1とGen2と呼ばれる子名前空間とサブフォルダーの下にあります。指定されたパラメーター(文字列、列挙型など)に基づいてIFooを返すことを目的としたIFooFactoryインターフェイスがあります。

この例では、列挙型を使用してわかりやすくしています。最初は文字列バージョンを作成しましたが、文字列などのより任意のパラメーターを渡すことに対する反対意見は、問題を混乱させるだけだと感じました。

public enum ImplementationGeneration
{
    Gen1,
    Gen2,
    Gen3,
}

public interface IFoo
{
    void DoStuff();
}

public interface IFooFactory
{
    IFoo CreateFoo(ImplementationGeneration implementationGeneration);
}


namespace SomeRootNamespace.Gen1
{
    public class Foo : IFoo
    {
        public void DoStuff()
        {
            Console.WriteLine("Doing Type1 stuff");
        }
    }
}

namespace SomeRootNamespace.Gen2
{
    public class Foo : IFoo
    {
        public void DoStuff()
        {
            Console.WriteLine("Doing Type2 stuff");
        }
    }
}

さて、このような実装をコンシューマーに「選択」させることは、理想的には存在しないカップリングの形式であることを理解していますが、私見では、Ninjectがすでにサポートしている名前付きバインディングと同じレベルのカップリングです。実装に属性を追加することは避けたかったのですが、GetGen1 / GetGen2 / etcメソッドをファクトリインターフェイスに含めることは、これに非常に適しています。呼び出し先のメソッドに入力をマップするために、どこかのスイッチを介してOCPに違反することになります。 (または手動でリフレクションを使用)

可能であれば避けたいと思っている完全な/動作するコードはここにあります:https ://gist.github.com/4549677

2つのアプローチを使用します。

  1. 列挙型のスイッチが渡されたOCPに違反する手動のファクトリ実装
  2. IInstanceProviderのインスタンスでファクトリ拡張を使用します(GetInstanceをオーバーライドするためにStandardInstanceProviderをサブクラス化します)。

2番目のアプローチは、これを機能させるために「正しい方法」に「近い」可能性があるように見えますが、1)作業を行うためにカーネルへの参照を保持します。これはおそらく悪い考えであり、2)呼び出し中にすべてのIFooバインディングを検索したときに、IFooのバインディングに具体的なタイプが見つかりませんでした。現在、GetAllを実行しているため、このシナリオに必要な数よりもN-1多いインスタンスをインスタンス化します。

4

1 に答える 1

1

ええと、少なくとも上記のものよりも優れたものを見つけました。

結局、名前付きバインディングを使用し、名前空間の最後の部分に基づいてすべてのバインディングに名前を付けるためにninject.extensions.conventionsが使用されます。これは、それを必要としない多くのバインディング (特定のインターフェースで利用可能な実装が 1 つしかないもの) に名前を付けることになりますが、それらのバインディングに名前を付けても、それらの使用に問題はありません (少なくとも私のテストでは)。 )。

何らかの理由で、将来これに遭遇する人にとって問題であった場合は、規則を介してバインディングを設定するコードをより具体的にすることができます-たとえば、最後の名前空間部分が.特定のセットまたは特定のパターンに一致します。

kernel.Bind(x => x
    .FromThisAssembly()
    .SelectAllClasses()
    .BindAllInterfaces()
    .Configure((binding, concreteType) =>
    {
        var concreteNamespace = concreteType.Namespace ?? String.Empty;
        var lastNamespacePart = concreteNamespace.Split('.').Last();
        binding.Named(lastNamespacePart);
    })
);

次に、UseFirstArgumentAsNameInstanceProviderを使用して、ファクトリ メソッドへの最初のパラメーターに基づいてバインディング名を検索します (したがって、GetGen1、GetGen2 などに個別のメソッドを用意する必要はありません)。実際の文字列の代わりに列挙型を渡していたので、GetName オーバーライドを変更して ToString を実行しましたが、それ以外はリンクされた wiki ページと同じです。

public class UseFirstArgumentAsNameInstanceProvider : StandardInstanceProvider
{
    protected override string GetName(System.Reflection.MethodInfo methodInfo, object[] arguments)
    {
        return arguments[0].ToString();
    }

    protected override ConstructorArgument[] GetConstructorArguments(System.Reflection.MethodInfo methodInfo, object[] arguments)
    {
        return base.GetConstructorArguments(methodInfo, arguments).Skip(1).ToArray();
    }
}

別の/より良いオプションが利用できる場合に備えて、しばらく質問を開いたままにしますが、少なくともこれは合理的であり、明らかなOCPの問題はありません. :)

于 2013-01-18T21:08:15.920 に答える