Autofac:将多个逆变实现隐藏在一个组合后面

发布于 2024-11-30 10:35:06 字数 3377 浏览 6 评论 0原文

我被这个关于 (. NET 4.0)对 Autofac 的协变和逆变支持,现在我正在尝试实现类似的目标,但没有任何运气。

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

换句话说,当我这样写时:

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

handler.Handle(new CustomerMovedEvent());

关于应用程序设计(如下所示),我希望返回一个包含 CustomerMovedEventHandlerMultipleDispatchEventHandler > 和一个 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 的定义:

// 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]] -> IEventHandler'1[[SpecialCustomerMovedEvent]][] -> MultipleDispatchEventHandler'1[[SpecialCustomerMovedEvent]]。

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

I was triggered by this SO question about (.NET 4.0) covariance and contravariance support for Autofac, and now I'm trying to achieve something similar, but without any luck.

What I am trying to achieve is configure Autofac in such way that when I resolve a single concrete IEventHandler<TEvent> (for the sake of demonstration using container.Resolve, but normally of course using constructor injection), Autofac will return me a MultipleDispatchEventHandler<TEvent> that wraps all registered event handlers that are assignable from the requested handler.

In other words, when I write this:

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

handler.Handle(new CustomerMovedEvent());

With respect to the application design (given below), I'd expect a MultipleDispatchEventHandler<CustomerMovedEvent> to be returned that wraps both a CustomerMovedEventHandler and a NotifyStaffWhenCustomerMovedEventHandler.

Here is the application design:

// 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) { ... }
}

This is the definition of the MultipleDispatchEventHandler<TEvent>, defined in the Composition Root:

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

This is my current configuration:

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

With the current configuration, the application fails during the call to Resolve, with the following exception:

Autofac.Core.DependencyResolutionException: Circular component
dependency detected:
MultipleDispatchEventHandler'1[[SpecialCustomerMovedEvent]] ->
IEventHandler'1[[SpecialCustomerMovedEvent]][] ->
MultipleDispatchEventHandler'1[[SpecialCustomerMovedEvent]].

Now the question is of course: how can I fix the configuration (or the design) to support this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

北方的韩爷 2024-12-07 10:35:06

我将把这个作为一个单独的答案,而不是修改我的另一个答案。这个解决方案不使用组合来解决示例场景。

工作代码

为了测试目的,我向每个事件处理程序添加了一个 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 而不是复合 IEventHandler。它看起来是这样的:

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 接口。我不应该要求任何人处理我的活动。我想做的就是提高它。处理可能会也可能不会发生在另一侧;这与我无关。我很自私 - 我想要一个专门为我创建的界面以及我引发事件的需要。

作为事件引发者,我打算引发一个事件。作为事件处理程序,我打算处理一个事件。我们有两个不同的意图,所以我们应该有两个不同的接口。仅仅因为我们可以使用相同的接口和组合并不意味着我们应该这样做。

接口隔离原则似乎更多的是关于将胖接口分割成更薄的接口(另请参阅角色接口)。在我们的例子中,我们没有胖接口,但我认为我们正在做类似的事情 - “按意图进行接口隔离”。

还有一件事

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

“C 型接口”——经常使用,很少实现。 “服务”界面。例如,IEventRaiserICustomerRepository。这些接口可能只有一个实现(可能进行了一些修饰),但它们被想要引发事件或拯救客户的代码到处使用。

“I 类接口”——经常实现,很少使用。 “插件”界面。例如,IEventHandler。仅在一个地方(EventRaiser)使用,但由许多类实现。

同一接口不应同时是 Type C 和 Type I。这是将 IEventRaiser(Type C)与 IEventHandler(Type I)分开的另一个原因。

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

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

I'm going to make this a separate answer instead of modifying my other one. This one solves the example scenario without using a composite.

Working Code

I added a static int handleCount to each of the event handlers for testing purposes, like this:

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

Here's a passing test that demonstrates that the events are going where they should:

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

You can see I'm using an IEventRaiser<TEvent> instead of a composite IEventHandler<TEvent>. Here's how it looks:

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

Design Thoughts

Avoiding the composite IEventHandler sure makes our work at the composition root easier. We don't have to worry about recursive composition or making sure the composite is the default implementation. But we added a new interface IEventRaiser which might look redundant. Is it? I think not.

Raising an event and handling an event are two different things. IEventHandler is an interface that has to do with handling events. IEventRaiser is an interface that has to do with raising events.

Imagine that I'm a piece of code that wants to raise an event. If I ask the IoC for a single IEventHandler I am introducing coupling that I don't need. I shouldn't need to know about that IEventHandler interface. I shouldn't be asking anyone to Handle my event. All I want to do is Raise it. Handling may or may not happen on the other side; it is irrelevant to me. I'm selfish - I want an interface created solely for me and my need to raise events.

As an event raiser, I intend to raise an event. As an event handler, I intend to handle an event. We have two different intents, so we should have two different interfaces. Just because we could use the same interface and a composite doesn't mean we should.

The Interface Segregation Principle seems to be more about splitting fat interfaces into thinner ones (see also Role Interface). In our case, we don't have a fat interface, but I think we're doing something similar - "Interface Segregation by Intent".

One more thing

In writing this answer I almost articulated a design idiom that I think many of us are familiar with, but I don't think we have standard terminology for it.

"Type C Interface" - frequently Consumed, rarely Implemented. A "service" interface. For example, IEventRaiser or ICustomerRepository. These interfaces probably have only one implementation (maybe decorated a bit) but they are consumed all over the place by code that wants to Raise Events or Save Customers.

"Type I Interface" - frequently Implemented, rarely Consumed. A "plugin" interface. For example, IEventHandler<TEvent>. Consumed in only one place (the EventRaiser) but implemented by many classes.

The same interface should not be both a Type C and a Type I. This is another reason to separate the IEventRaiser (Type C) from the IEventHandler (Type I).

I'm thinking that the composite pattern is only applicable to Type C interfaces.

Please edit or comment if there is standard terminology for what I've called "Type C" and "Type I" interfaces.

冷血 2024-12-07 10:35:06

@default.kramer 为 IEventRaiser +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)));

+1 for IEventRaiser<T> by @default.kramer. Just for the record, since the linked answer doesn't provide any code, and the configuration for this scenario is a bit less than intuitive because of the generic types involved:

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)));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文