指定要在运行时从类型工厂解析的组件名称

时间:2018-02-12 15:25:10

标签: c# .net dependency-injection castle-windsor

假设我有一个界面:

public interface IService
{
    void DoThing();
}

现在让我们说我想要一些这样的实现,所以我可以换掉我想在运行时使用的实现:

container.Register(Component.For<IService>().ImplementedBy<IServiceImplementationOne>().Named("First"));
container.Register(Component.For<IService>().ImplementedBy<IServiceImplementationTwo>().Named("Second"));
container.Register(Component.For<IService>().ImplementedBy<IServiceImplementationThree>().Named("Third"));

所以我可以通过名字选择一个:

container.Resolve<IService>("First"); //returns IServiceImplementationOne
container.Resolve<IService>("Second"); //returns IServiceImplementationTwo
container.Resolve<IService>("Third"); //returns IServiceImplementationThree

现在我希望能够使用内置类型工具在运行时选择我想要的那个,如果Windsor可以自动执行此操作,那么代码库中没有更多代码。

所以,让我们说我现在有工厂服务:

public interface IServiceFactory
{
     IService Get(string name);
}

如果我这样注册:

container.Register(Component.For<IServiceFactory>().AsFactory());

如果我尝试拨打Get("First"),我会收到一个例外情况,说它无法找到名为''的注册。

根据文档,您的方法名称中Get之后的任何内容都将用作要解析的名称,因此您可以在工厂界面中使用GetXXX()将其归结为{{1}我希望只使用container.Resolve<IService>("XXX")作为方法名称让我在运行时指定它,但显然情况并非如此。

文档还说你可以在方法名称中使用单词Get来将构造函数参数转发给工厂创建的组件,但这不是我想要的。

内置类型工厂是否允许此行为?我意识到我可以实现一个自定义Create,但如果我走得那么远,那么我也可以实现我自己的具体工厂类。

1 个答案:

答案 0 :(得分:3)

  

我意识到我可以实现一个自定义的ITypedFactoryComponentSelector,但如果我走得那么远,那么我也可以实现我自己的具体工厂类。

如果您实施自己的ITypedFactoryComponentSelector,那么您将创建逻辑,告知工厂选择哪个实施。但是一旦做出决定,所选的实现仍然是从容器中解决的。这比仅仅实施自己的混凝土工厂类更有利。如果实现是从容器中解析出来的,那么它们可以有自己的独立依赖关系,这对容器来说很容易管理,但如果具体工厂必须详细了解如何构造这些对象及其依赖关系等等,那就更多了。< / p>

如果您正在尝试获取命名实现,并且名称是运行时可用的字符串,那么这非常简单。

public class MyComponentSelector : DefaultTypedFactoryComponentSelector
{
    protected override string GetComponentName(MethodInfo method, object[] arguments)
    {
        return arguments[0].ToString();
    }
}

它只是获取您传入的字符串并将其用作组件名称。无论您传递给工厂的Create方法的名称是什么,这都是返回的组件名称。

如果你传入一些其他的类或值,这个更有用,这个选择器可以根据输入确定组件名称。

这里是一个更详细的例子,其中组件名称不是直接从传递给工厂的字符串中选择的,而是由传入的对象决定的。自从我们的应用程序以来,它更加实用一些代码不太可能知道DI设置中的组件名称。

这是一个选择器。它需要一个Address对象,并使用该地址中的国家/地区代码来选择AddressValidator实现的组件名称。它还指定使用后备,以便&#34;泛型&#34;如果没有匹配,可以使用地址验证器。

public class AddressValidatorSelector : DefaultTypedFactoryComponentSelector
{
    public AddressValidatorSelector() 
        : base(fallbackToResolveByTypeIfNameNotFound: true) { }

    protected override string GetComponentName(MethodInfo method, object[] arguments)
    {
        return "AddressValidatorFor_" + ((Address)arguments[0]).CountryCode;
    }
}

然后是实际的组件注册。它记录了几个特定的​​实现,后备和工厂本身。当它注册工厂时,它指定它将使用AddresssValidatorSelector

public class WindsorInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.AddFacility<TypedFactoryFacility>();
        container.Register(
            Component.For<IAddressValidator,UnitedStatesAddressValidator>()
                .Named("AddressValidatorFor_USA"),
            Component.For<IAddressValidator, FinlandAddressValidator>()
                .Named("AddressValidatorFor_FIN"),
            Component.For<IAddressValidator, MalawiAddressValidator>()
                .Named("AddressValidatorFor_MWI"),
            Component.For<IAddressValidator, CommonCountryAddressValidator>()
                .Named("FallbackCountryAddressValidator")
                .IsDefault()            
            );
        container.Register(
            Component.For<IAddressValidatorFactory>()
                .AsFactory(new AddressValidatorSelector())
            );
    }
}

这也允许您为不同的名称重复使用相同的组件。例如,假设法国和德国都使用相同的欧盟特定验证器 - 您可以在两个名称下注册相同的实现。