Castle Windsor:实现2级(嵌套)工厂的更好方法?

Bin*_*ier 3 factory castle-windsor factory-pattern

我们有一个我们多次使用的模式,我们在不同的Dll中实现处理程序和工厂.我们在运行时配置exe,说明加载了哪些dll,以及应用程序可以使用哪些处理程序.

我们这样做是因为我们为一些客户提供了自定义处理,同时它还具有很大的灵活性,因为我们可以快速开发新的处理程序,并且可以放心地测试和部署它们,我们甚至没有触及正在运行的应用程序的任何其他部分.我们还可以通过简单地放入一个替换dll来修补处理程序,我们让客户拥有严格的变更管理程序并且他们喜欢这个.

要做到这一点,模式依赖于两个级别的工厂,实现特定处理程序的特定工厂,以及一个总体工厂(我们称之为提供者).Provider选择使用哪个处理程序工厂来创建处理程序.

问题:Windsor是否包含可以简化此过程的内容?
具体来说,我正在寻找可以省略Handler工厂对象的东西,它感觉就像它应该能做的事情.
我已经阅读了Typed Factory Facility和UsingFactory&UsingFactoryMethodmethods,但我看不出他们在这里有什么帮助.
也就是说我经常发现Castle Windsor的文档是钝的,所以我可能会遗漏一些明显的东西 或者是否有更好的方法来获得我没有考虑过的相同的最终目标.

这里有一些代码来说明,第一个消息,处理程序和工厂接口

public interface IMessage
{
    string MessageType { get; }
}
public interface IMessageHandler
{
    void Process(IMessage message);
}
public interface IMessageHandlerFactory
{
    bool CanProcessType(string type);
    IMessageHandler Create();
}
Run Code Online (Sandbox Code Playgroud)

在第二个DLL中,我们为Type1实现了一个处理程序和工厂

public class Type1MessageHandler
    : IMessageHandler
{
    public void Process(IMessage message) { }
}
public class Type1MessageHandlerFactory
    : IMessageHandlerFactory
{
    public bool CanProcessType(string type)
    {
        return type == "Type1";
    }
    public IMessageHandler Create()
    {
        return new Type1MessageHandler();
    }
}
Run Code Online (Sandbox Code Playgroud)

在第三个Dll中,我们为Type2实现了一个处理程序和工厂

public class Type2MessageHandler
    : IMessageHandler
{
    public void Process(IMessage message) { }
}
public class Type2MessageHandlerFactory
    : IMessageHandlerFactory
{
    public bool CanProcessType(string type)
    {
        return type == "Type2";
    }
    public IMessageHandler Create()
    {
        return new Type2MessageHandler();
    }
}
Run Code Online (Sandbox Code Playgroud)

在Windows服务中,我们实现了提供程序

public interface IMessageHandlerProvider
{
    IMessageHandler Create(string messageType);
}
public class MessageHandlerProvider
    : IMessageHandlerProvider
{
    IEnumerable<IMessageHandlerFactory> factories;
    public MessageHandlerProvider(IWindsorContainer wc)
    {
        factories = wc.ResolveAll<IMessageHandlerFactory>();
    }
    public IMessageHandler Create(string messageType)
    {
        foreach (var factory in factories)
            if (factory.CanProcessType(messageType))
                return factory.Create();
        throw new UnableToFindMessageHandlerFactoryForType(messageType);
    }
}
Run Code Online (Sandbox Code Playgroud)

实际需要处理程序的服务仅使用Provider

public class MessageService
{
    public MessageService(IMessageHandlerProvider handlerProvider) {}
}
Run Code Online (Sandbox Code Playgroud)

sam*_*amy 5

在温莎有类型的工厂,你问的确是可能的; 而不是解析提供程序中的所有工厂,然后查找可以处理消息的工厂,您可以向Windsor询问链接到消息类型的处理程序并使用它.你真的不需要二级工厂(IMessageHandlerFactory),因为处理程序可以告诉它将链接到哪个消息.

这是一个很好的资源,这个架构(你可能已经读过这个),我将很快总结.

给定您的接口,首先注册所有处理程序

container.Register(Classes.FromAssemblyInThisApplication()
    .BasedOn<IMessageHandler>()
    .WithServiceAllInterfaces());
Run Code Online (Sandbox Code Playgroud)

好的,现在让我们告诉Windsor我们想要一家能够退货的工厂IMessageHandler.好的是我们实际上不需要为工厂编写任何代码.

container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IMessageHandlerProvider>().AsFactory());
Run Code Online (Sandbox Code Playgroud)

现在我们可以开始使用工厂了

var provider = container.Resolve<IMessageHandlerProvider>();
var msg = new Type2Message();
var msgHandler = provider.Create(msg.MessageType);
Run Code Online (Sandbox Code Playgroud)

问题是,由于我们的消息处理程序和传递给工厂的字符串之间没有链接,因此Windsor返回IMessageHandler它找到的第一个已注册的实例.为了创建这个链接,我们可以在它应该处理的消息类型之后命名每个消息处理程序.

您可以通过多种方式执行此操作,但我喜欢创建一个约定,其中消息处理程序类型告诉它可以处理哪些消息:

container.Register(Classes.FromAssemblyInThisApplication()
    .BasedOn<IMessageHandler>()
    .WithServiceAllInterfaces().Configure(c => {
        c.Named(c.Implementation.Name.Replace("MessageHandler", string.Empty));
    }));
Run Code Online (Sandbox Code Playgroud)

现在,您需要告诉工厂必须将消息类型用作要解析的处理程序的名称.为此,可以使用继承的类DefaulTypedFactoryComponentSelector.我们只是覆盖组件名称的确定方式,并返回我们收到的消息类型:

public class MessageHandlerSelector : DefaultTypedFactoryComponentSelector
{
    protected override string GetComponentName(MethodInfo method, object[] arguments)
    {
        return arguments[0].ToString();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以在工厂中插入这个选择器

container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IMessageHandlerProvider>()
     .AsFactory(c =>c.SelectedWith(new MessageHandlerSelector())));
Run Code Online (Sandbox Code Playgroud)

以下是处理任何消息的完整代码:

var container = new WindsorContainer();
container.Register(Classes.FromAssemblyInThisApplication()
    .BasedOn<IMessageHandler>()
    .WithServiceAllInterfaces().Configure(c => {
        c.Named(c.Implementation.Name.Replace("MessageHandler", string.Empty));
}));

container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IMessageHandlerProvider>().AsFactory(c =>c.SelectedWith(new MessageHandlerSelector())));

var provider = container.Resolve<IMessageHandlerProvider>();
var msg = new Type2Message();
var msgHandler = provider.Create(msg.MessageType);
msgHandler.Process(msg);
Run Code Online (Sandbox Code Playgroud)

以下是我想强调的一些观点:

  • 你猜对了,你不需要两个工厂:一个就足够了
  • 消息处理程序的命名约定不是一成不变的,您可以决定使用另一种机制来覆盖约定
  • 我不是在谈论发布组件,但链接包含一些有关它的信息,您应该查看
  • 我没有处理没有找到处理程序的情况,但是当它无法使用a解析处理程序时,Castle会自行抛出 ComponentNotFoundException
  • 如果处理程序明确表示它们处理的消息类型,那么系统可能会更强大.例如改变接口到IHandlerOf<T>与T作为一个消息类型的实现.