【问题标题】:Castle Windsor: A better way to implement 2 levels of (nested) factories?Castle Windsor:实现 2 级(嵌套)工厂的更好方法?
【发布时间】:2014-08-25 16:28:11
【问题描述】:

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

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

为此,该模式依赖于两个级别的工厂,实现特定处理程序的特定工厂和一个总体工厂(我们称之为 Provider)。 Provider 选择使用哪个处理程序工厂来创建处理程序。

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

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

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

在第二个 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();
    }
}

在第三个 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();
    }
}

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

真正需要handlers的服务只使用Provider

public class MessageService
{
    public MessageService(IMessageHandlerProvider handlerProvider) {}
}

【问题讨论】:

    标签: castle-windsor factory factory-pattern


    【解决方案1】:

    您所问的问题在温莎的类型工厂中确实是可能的;与其解析提供程序中的所有工厂,然后寻找可以处理消息的工厂,不如向 Windsor 询问链接到消息类型的处理程序并使用它。您实际上并不需要二级工厂 (IMessageHandlerFactory),因为处理程序可以知道它将链接到什么消息。

    这是nice resource for this architecture(您可能已经阅读过this one),我会很快总结一下。

    根据您的接口,您首先要注册所有处理程序

    container.Register(Classes.FromAssemblyInThisApplication()
        .BasedOn<IMessageHandler>()
        .WithServiceAllInterfaces());
    

    好的,现在让我们告诉 Windsor 我们想要一个返回 IMessageHandler 的工厂。好的是我们实际上不必为工厂编写任何代码。

    container.AddFacility<TypedFactoryFacility>();
    container.Register(Component.For<IMessageHandlerProvider>().AsFactory());
    

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

    var provider = container.Resolve<IMessageHandlerProvider>();
    var msg = new Type2Message();
    var msgHandler = provider.Create(msg.MessageType);
    

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

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

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

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

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

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

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

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

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

    以下是我想强调的几点:

    • 如您所料,您不需要两个工厂:一个就足够了
    • 消息处理程序的命名约定并非一成不变,您可以决定采用另一种机制来覆盖约定
    • 我不是在谈论发布组件,但链接包含一些您应该查看的信息
    • 我没有处理找不到处理程序的情况,但是当Castle无法使用ComponentNotFoundException解析处理程序时,它会自行抛出
    • 如果处理程序明确说明它们处理的消息类型,系统可能会更加健壮。例如,将接口更改为IHandlerOf&lt;T&gt;T 是消息类型实现。

    【讨论】:

    • 刚刚进行了快速扫描,如果这确实听起来完全一样。就像我正在寻找的那样。早上起来好好检查一下。谢谢队友:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多