Autofac:隐藏一个复合背后的多个逆变实现

时间:2011-08-21 15:04:08

标签: c# dependency-injection ioc-container autofac covariance

我被this SO question关于(.NET 4.0)协方差和逆向支持Autofac所触发,现在我正在努力实现类似的东西,但没有任何运气。

我想要实现的是以这样的方式配置Autofac:当我解析单个具体IEventHandler<TEvent>时(为了使用container.Resolve演示,但通常使用构造函数注入),Autofac将返回一个MultipleDispatchEventHandler<TEvent>,它包装所有已从请求的处理程序分配的已注册事件处理程序。

换句话说,当我写这篇文章时:

var handler = container
    .GetInstance<IEventHandler<CustomerMovedEvent>>();

handler.Handle(new CustomerMovedEvent());

关于应用程序设计(如下所示),我希望返回一个包含MultipleDispatchEventHandler<CustomerMovedEvent>CustomerMovedEventHandler的{​​{1}}。

以下是应用程序设计:

NotifyStaffWhenCustomerMovedEventHandler

这是在组合根中定义的// Events: public class CustomerMovedEvent { } public class CustomerMovedAbroadEvent : CustomerMovedEvent { } public class SpecialCustomerMovedEvent : CustomerMovedEvent { } // Event handler definition (note the 'in' keyword): public interface IEventHandler<in TEvent> { void Handle(TEvent e); } // Event handler implementations: public class CustomerMovedEventHandler : IEventHandler<CustomerMovedEvent> { public void Handle(CustomerMovedEvent e) { ... } } public class NotifyStaffWhenCustomerMovedEventHandler : IEventHandler<CustomerMovedEvent> { public void Handle(CustomerMovedEvent e) { ... } } public class CustomerMovedAbroadEventHandler : IEventHandler<CustomerMovedAbroadEvent> { public void Handle(CustomerMovedAbroadEvent e) { ... } } 的定义:

MultipleDispatchEventHandler<TEvent>

这是我目前的配置:

// A composite wrapping possibly multiple handlers.
public class MultipleDispatchEventHandler<TEvent>
    : IEventHandler<TEvent>
{
    private IEnumerable<IEventHandler<TEvent>> handlers;

    public MultipleDispatchEventHandler(
        IEnumerable<IEventHandler<TEvent>> handlers)
    {
        this.handlers = handlers;
    }

    public void Handle(TEvent e)
    {
        this.handlers.ToList().ForEach(h => h.Handle(e));
    }
}

使用当前配置,应用程序在调用var builder = new ContainerBuilder(); // Note the use of the ContravariantRegistrationSource (which is // available in the latest release of Autofac). builder.RegisterSource(new ContravariantRegistrationSource()); builder.RegisterAssemblyTypes(typeof(IEventHandler<>).Assembly) .AsClosedTypesOf(typeof(IEventHandler<>)); // UPDATE: I'm registering this last as Kramer suggests. builder.RegisterGeneric(typeof(MultipleDispatchEventHandler<>)) .As(typeof(IEventHandler<>)).SingleInstance(); var container = builder.Build(); 期间失败,但出现以下异常:

  

Autofac.Core.DependencyResolutionException:循环组件   检测到依赖:   MultipleDispatchEventHandler'1 [[SpecialCustomerMovedEvent]] - &gt;   IEventHandler'1 [[SpecialCustomerMovedEvent]] [] - &gt;   MultipleDispatchEventHandler'1 [[SpecialCustomerMovedEvent]。

现在的问题当然是:如何修复配置(或设计)来支持这个?

2 个答案:

答案 0 :(得分:7)

我要把它作为一个单独的答案,而不是修改我的另一个。这个解决了示例场景而不使用复合。

工作代码

为了测试目的,我为每个事件处理程序添加了static int handleCount,如下所示:

public class CustomerMovedEventHandler
    : IEventHandler<CustomerMovedEvent>
{
    public static int handleCount = 0;
    public void Handle(CustomerMovedEvent e) { handleCount++; }
}

这是一个通过测试,证明事件正在他们应该去的地方:

var builder = new ContainerBuilder();

builder.RegisterSource(new Autofac.Features
    .Variance.ContravariantRegistrationSource());

builder.RegisterAssemblyTypes(typeof(IEventHandler<>).Assembly)
    .AsClosedTypesOf(typeof(IEventHandler<>));

builder.RegisterGeneric(typeof(EventRaiser<>))
    .As(typeof(IEventRaiser<>));

var container = builder.Build();

Assert.AreEqual(0, CustomerMovedEventHandler.handleCount);
Assert.AreEqual(0, NotifyStaffWhenCustomerMovedEventHandler.handleCount);
Assert.AreEqual(0, CustomerMovedAbroadEventHandler.handleCount);

container.Resolve<IEventRaiser<CustomerMovedEvent>>()
    .Raise(new CustomerMovedEvent());

Assert.AreEqual(1, CustomerMovedEventHandler.handleCount);
Assert.AreEqual(1, NotifyStaffWhenCustomerMovedEventHandler.handleCount);
Assert.AreEqual(0, CustomerMovedAbroadEventHandler.handleCount);

container.Resolve<IEventRaiser<CustomerMovedAbroadEvent>>()
    .Raise(new CustomerMovedAbroadEvent());

Assert.AreEqual(2, CustomerMovedEventHandler.handleCount);
Assert.AreEqual(2, NotifyStaffWhenCustomerMovedEventHandler.handleCount);
Assert.AreEqual(1, CustomerMovedAbroadEventHandler.handleCount);

container.Resolve<IEventRaiser<SpecialCustomerMovedEvent>>()
    .Raise(new SpecialCustomerMovedEvent());

Assert.AreEqual(3, CustomerMovedEventHandler.handleCount);
Assert.AreEqual(3, NotifyStaffWhenCustomerMovedEventHandler.handleCount);
Assert.AreEqual(1, CustomerMovedAbroadEventHandler.handleCount);

您可以看到我使用IEventRaiser<TEvent>而不是复合IEventHandler<TEvent>。这是它的外观:

public interface IEventRaiser<TEvent>
{
    void Raise(TEvent e);
}

public class EventRaiser<TEvent> : IEventRaiser<TEvent>
{
    List<IEventHandler<TEvent>> handlers;

    public EventRaiser(IEnumerable<IEventHandler<TEvent>> handlers)
    {
        this.handlers = handlers.ToList();
    }

    public void Raise(TEvent e)
    {
        handlers.ForEach(h => h.Handle(e));
    }
}

设计思想

避免复合IEventHandler确保我们在作品根处的工作更容易。我们不必担心递归组合或确保复合是默认实现。但是我们添加了一个可能看起来多余的新接口IEventRaiser。是吗?我想不是。

举办活动和处理活动是两回事。 IEventHandler是一个与处理事件有关的界面。 IEventRaiser是一个与提升事件有关的界面。

想象一下,我是一段想要举办活动的代码。如果我向IoC询问一个IEventHandler我正在引入我不需要的耦合。我不应该知道IEventHandler接口。我不应该要求任何人Handle我的活动。我想做的就是Raise它。处理可能会也可能不会发生在另一方面;这与我无关。我很自私 - 我想要一个专门为我创建的界面以及我需要举办活动。

作为一名活动提升者,我打算举办活动。作为事件处理程序,我打算处理一个事件。我们有两个不同的意图,所以我们应该有两个不同的接口。仅仅因为我们可以使用相同的界面和复合材料并不意味着我们应该这样做。

Interface Segregation Principle似乎更多的是将胖接口拆分为更薄接口(另请参阅Role Interface)。在我们的例子中,我们没有胖接口,但我认为我们正在做类似的事情 - “Intent接口隔离”。

还有一件事

在写这个答案时,我几乎阐述了一个我认为很多人都熟悉的设计习语,但我认为我们没有标准的术语。

“C型接口” - 经常消费,很少实施。一个“服务”界面。例如,IEventRaiserICustomerRepository。这些接口可能只有一个实现(可能有点装饰),但是它们会被想要提升事件或节省客户的代码消耗掉。

“Type I Interface” - 经常实施,很少消费。一个“插件”界面。例如,IEventHandler<TEvent>。仅在一个地方(EventRaiser)消费,但由许多类实施。

同一个界面不应该是C型和I型。这是将IEventRaiser(C型)与IEventHandler(I型)分开的另一个原因。

我认为复合模式仅适用于Type C接口。

如果有我称之为“Type C”和“Type I”接口的标准术语,请编辑或评论。

答案 1 :(得分:6)

@ default.kramer的IEventRaiser<T>为+1。仅供记录,因为链接的答案不提供任何代码,并且由于涉及的泛型类型,此方案的配置有点不直观:

builder.RegisterSource(new ContravariantRegistrationSource());

builder.RegisterAssemblyTypes(...)
    .As(t => t.GetInterfaces()
        .Where(i => i.IsClosedTypeOf(typeof(IEventHandler<>)))
        .Select(i => new KeyedService("handler", i)));

builder.RegisterGeneric(typeof(MultipleDispatchEventHandler<>))
    .As(typeof(IEventHandler<>))
    .WithParameter(
         (pi, c) => pi.Name == "handlers",
         (pi, c) => c.ResolveService(
             new KeyedService("handler", pi.ParameterType)));