Ste*_*ven 14 c# dependency-injection ioc-container covariance autofac
我被这个关于(.NET 4.0)协方差和逆向支持Autofac的问题引发了,现在我正试图实现类似的东西,但没有任何运气.
我试图做到的,是在这样的方式配置Autofac,当我解决一个具体的IEventHandler<TEvent>(使用示范的目的container.Resolve,但通常的过程中使用构造器注入),Autofac将返回我MultipleDispatchEventHandler<TEvent>是包装所有注册的事件处理程序,它可分配来自请求的处理程序.
换句话说,当我写这个:
var handler = container
.GetInstance<IEventHandler<CustomerMovedEvent>>();
handler.Handle(new CustomerMovedEvent());
Run Code Online (Sandbox Code Playgroud)
关于应用程序设计(如下所示),我希望MultipleDispatchEventHandler<CustomerMovedEvent>返回包含a CustomerMovedEventHandler和a的应用程序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) { ... }
}
Run Code Online (Sandbox Code Playgroud)
这是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));
}
}
Run Code Online (Sandbox Code Playgroud)
这是我目前的配置:
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();
Run Code Online (Sandbox Code Playgroud)
使用当前配置,应用程序在调用期间失败Resolve,但出现以下异常:
Autofac.Core.DependencyResolutionException:圆形组件相关检测:MultipleDispatchEventHandler'1 [[SpecialCustomerMovedEvent〕〕 - > IEventHandler'1 [[SpecialCustomerMovedEvent]] [] - > MultipleDispatchEventHandler'1 [[SpecialCustomerMovedEvent].
现在的问题当然是:如何修复配置(或设计)以支持这一点?
我将把它作为一个单独的答案,而不是修改我的另一个.这个解决了不使用复合的示例场景.
static int handleCount为了测试目的,我在每个事件处理程序中添加了一个,如下所示:
public class CustomerMovedEventHandler
: IEventHandler<CustomerMovedEvent>
{
public static int handleCount = 0;
public void Handle(CustomerMovedEvent e) { handleCount++; }
}
Run Code Online (Sandbox Code Playgroud)
这是一个通过测试,证明事件正在他们应该去的地方:
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);
Run Code Online (Sandbox Code Playgroud)
你可以看到我使用的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));
}
}
Run Code Online (Sandbox Code Playgroud)
避免复合IEventHandler确保使我们在组合根处的工作更容易.我们不必担心递归组合或确保复合是默认实现.但我们添加了一个IEventRaiser可能看起来多余的新界面.是吗?我想不是.
举办活动和处理活动是两回事. IEventHandler是一个与处理事件有关的接口.IEventRaiser是一个与引发事件有关的接口.
想象一下,我是一段想要举办活动的代码.如果我问IoC一个IEventHandler我引入我不需要的耦合.我不需要知道那个IEventHandler界面.我不应该要求任何人参加Handle我的活动.我想做的就是Raise它.处理可能会也可能不会发生在另一方面; 这与我无关.我很自私 - 我想要一个专门为我创建的界面以及我需要提升事件.
作为一名赛事提升者,我打算举办一场活动.作为事件处理程序,我打算处理一个事件.我们有两个不同的意图,所以我们应该有两个不同的接口.仅仅因为我们可以使用相同的界面和复合材料并不意味着我们应该这样做.
该接口分离原则似乎更为折中一下脂肪接口转换成更薄的人(另见角色界面).在我们的例子中,我们没有胖接口,但我认为我们正在做类似的事情 - "Intent接口隔离".
还有一件事
在写这个答案时,我几乎阐述了一个我认为很多人都熟悉的设计习语,但我认为我们没有标准的术语.
"C型接口" - 经常消费,很少实施.一个"服务"界面.例如,IEventRaiser或ICustomerRepository.这些接口可能只有一个实现(可能有点装饰)但是它们会被想要提升事件或节省客户的代码消耗掉.
"I型接口" - 经常实施,很少消费.一个"插件"界面.例如,IEventHandler<TEvent>.仅在一个地方消费,EventRaiser但由许多类实施.
同一接口不应该是Type C和Type I.这是将IEventRaiser(Type C)与IEventHandler(Type I)分开的另一个原因.
我认为复合模式仅适用于Type C接口.
如果我称之为"Type C"和"Type I"接口的标准术语,请编辑或注释.
IEventRaiser<T>@ default.kramer 为+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)));
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2090 次 |
| 最近记录: |