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

Specify component name to resolve from type factory at runtime

假设我有一个接口:

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() 将其归结为 container.Resolve<IService>("XXX"),我希望只使用 Get 作为方法名称让我在运行时指定它,但显然情况并非如此。

文档还说您可以在方法名称中使用 Create 一词,以便能够将构造函数参数转发给工厂创建的组件,但这也不是我想要的。

内置类型化工厂是否允许这种行为?我意识到我可以实现自定义 ITypedFactoryComponentSelector,但如果我要走那么远,那么我也可以实现自己的具体工厂 class。

I realise I can implement a custom ITypedFactoryComponentSelector but if I'm going that far then I may as well just implement my own concrete factory class anyway.

如果您实现自己的 ITypedFactoryComponentSelector,那么您正在创建逻辑,告诉工厂要 select 实现哪个。但是一旦做出该决定,selected 实现仍在从容器中解析。与仅实施您自己的具体工厂 class 相比,这是一个巨大的好处。如果实现是从容器中解析出来的,那么它们可以有自己的独立依赖关系,这对容器来说很容易管理,但如果具体工厂必须详细了解如何构建这些对象及其依赖关系等,则需要更多工作。

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

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

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

如果您传入一些其他 class 或值,并且此 selector 根据输入确定组件名称,这将更有用。

这是一个更详细的示例,其中组件名称不是直接从传递给工厂的字符串 select 编辑的,而是由传入的对象确定的。这更实用一些,因为我们的应用程序代码不太可能知道我们的 DI 设置中的组件名称。

这是一个 selector。它需要一个 Address 对象,并使用从地址到 select 组件名称的国家/地区代码来实现 AddressValidator。它还指定了回退的使用,以便在没有匹配项时可以使用 "generic" 地址验证器。

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())
            );
    }
}

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