1

我正在使用来自 NuGet 的 Ninject 3.0.1.10 和 ninject.extensions.factory 3.0.1.0 - 在“真实”场景中,我也将使用 ninject.extensions.conventions(而不是手动绑定 IFoo),但我想将其排除在外以尝试简化问题。

我有一个 IFoo 接口和它的多个实现,每个实现都在一个子命名空间和名为 Gen1 和 Gen2 的子文件夹下。我有一个 IFooFactory 接口,其目的是它根据指定的参数(字符串、枚举等)返回一个 IFoo。

我在这个例子中使用枚举只是为了让它更清楚 - 我最初制作了一个字符串版本,但感觉像传递一个更任意的参数(如字符串)的反对意见只会混淆这个问题。

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

它使用两种方法:

  1. 手动工厂实现违反了 OCP 并通过了 enum 上的开关
  2. 使用带有 IInstanceProvider 实例的工厂扩展(子类 StandardInstanceProvider 以覆盖 GetInstance)。

第二种方法似乎“接近”让这个工作的“正确方法”,但是 1)它保持对内核的引用以完成它的工作,这可能是一个坏主意,并且 2)因为在调用期间搜索所有 IFoo 绑定时,我在 IFoo 绑定中找不到具体类型,它当前执行 GetAll,因此它实例化的实例比此场景所需的实例多 N-1 个。

4

1 回答 1

1

好吧,我发现至少比上面的更好。

毕竟它使用命名绑定,ninject.extensions.conventions用于根据名称空间的最后部分命名所有绑定。这最终会将名称附加到许多不需要它的绑定(对于给定接口只有一个实现可用的绑定),尽管将名称附加到这些绑定不会导致它们的使用出现任何问题(至少在我的测试中) )。

如果由于某种原因这对于将来遇到此问题的人来说是一个问题,您可以在通过约定设置绑定的代码中更具体 - 例如,如果最后一个命名空间部分位于特定的集合或匹配特定的模式。

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 回答