use*_*711 4 c# generics domain-events
我在这里提出一个问题:为多个订阅者提升域事件,答案让我得到以下模式,我可以像这样拥有一个IEventPublisher:
public interface IEventPublisher<T>
{
void Publish(T data);
}
Run Code Online (Sandbox Code Playgroud)
和像这样的IEventSubscriber:
public interface IEventSubscriber<T>
{
void Handle(T data);
}
Run Code Online (Sandbox Code Playgroud)
这个问题是我需要将每个发布者的实例传递给构造函数,如下所示:
public Service(IEventPublisher<ThingyChangedEvent> publisherThingyChanged)
{
// Set publisher to local variable
}
// then call this in a method
_publisherThingyChanged.Publish(new ThingyChangedEvent { ThingyId = model.Id});
Run Code Online (Sandbox Code Playgroud)
理想情况下,我希望能够拥有一个包含任何IEventPublishers的通用发布者,所以我可以调用类似的东西:
_genericPublisher.Publish(new ThingyChangedEvent { ThingyId = model.Id});
Run Code Online (Sandbox Code Playgroud)
我无法弄清楚如何做到这一点,因为我无法传递IEventPublisher的集合而没有像在ThingyChangedEvent中那样定义T,而我想要的是根据传递给通用发布者的类型来确定发布者.
任何建议非常感谢.
编辑:
好的,使用下面的答案和来自这里的一些信息:http://www.udidahan.com/2009/06/14/domain-events-salvation/我想出了以下内容,但它并不完全存在:
public interface IEventManager
{
void Publish<T>(T args) where T : IEvent;
}
Run Code Online (Sandbox Code Playgroud)
public class EventManager:IEventManager {Autofac.ILifetimeScope _container;
public EventManager(Autofac.ILifetimeScope container)
{
_container = container;
}
//Registers a callback for the given domain event
public void Publish<T>(T args) where T : IEvent
{
var subscribersProvider = _container.Resolve<IEventSubscribersProvider<T>>();
foreach (var item in subscribersProvider.GetSubscribersForEvent())
{
item.Handle(args);
}
}
Run Code Online (Sandbox Code Playgroud)
}
我现在可以在autofac解析的构造函数中获取IEventManager eventManager的实例,并按如下方式调用它:
_eventManager.Publish<ThingyChangedEvent>(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() });
Run Code Online (Sandbox Code Playgroud)
以下是我不喜欢这个解决方案:
我不想在构造函数中使用ILifetimeScope的实例,我希望能够获取IEventSubscribersProvider的集合,但如果我要求说,autofac将无法解决此问题:
IEnumerable<IEventSubscribersProvider<IEvent>>
Run Code Online (Sandbox Code Playgroud)
如果我将类型传递给Publish并调用,我只能解决它:
Resolve<IEventSubscribersProvider<T>>.
Run Code Online (Sandbox Code Playgroud)
第二个问题并不是一个大问题,但能够调用发布而不必像这样传递类型会很好:
_eventManager.Publish(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() });
Run Code Online (Sandbox Code Playgroud)
我想如果有人对如何解决这两个问题有任何建议,特别是问题1,因为我不喜欢在不同的项目中依赖Autofac.我能想出的唯一一个是某种类型的管理器类,它明确地采用了我需要的内容,如下所示:
public SomeConstructor(
IEventSubscribersProvider<ThingyChangedEvent> thingChangedSubscribeProviders,
IEventSubscribersProvider<SomeOtherEvent> someOtherSubscribeProviders,
etc....)
{
// Maybe take the EventManager as well and add them to it somehow but would be
// far easier to take a collection of these objects somehow?
}
Run Code Online (Sandbox Code Playgroud)
非常感谢任何建议.
编辑2
经过大量研究并在运行时查看此Autofac通用服务解析后,我不确定我能达到我想要的效果.我能想出的最佳解决方案是:
public interface IEventSubscribersProviderFactory : Amico.IDependency
{
IEventSubscribersProvider<T> Resolve<T>() where T : IEvent;
}
public class EventSubscribersProviderFactory : IEventSubscribersProviderFactory
{
Autofac.ILifetimeScope _container;
public EventSubscribersProviderFactory(Autofac.ILifetimeScope container)
{
_container = container;
}
public IEventSubscribersProvider<T> Resolve<T>() where T : IEvent
{
return _container.Resolve<IEventSubscribersProvider<T>>();
}
}
Run Code Online (Sandbox Code Playgroud)
然后让EventManager在构造函数中使用IEventSubscribersProviderFactory来从该项目中删除对Autofac的依赖.
我现在会继续这样做,但希望从长远来看能找到更好的解决方案.
当你必须处理多种类型的事件时,它会变得有点复杂.正如您可能已经注意到,您不能只使用派生泛型类型并希望像基础泛型一样使用它--.NET方差不支持您想要使用它的地方.
您需要一个"基础"类型,它是您接受为"事件"的最小(或最窄)类型.我通常使用一个标记接口一样public interface IEvent{}.当然,你可以从中得到Object; 但我觉得有一个标记界面可以使您订阅或发布"事件"显而易见(并且意味着您不能只发布任何类型的对象,只是"事件").
从多种类型方面的处理,您需要编写一个类来进行缩小(获取派生类型并"发布"相同的对象强制转换为派生类型).即便如此,您还需要通过较窄的适当实例来路由您的事件(以"绕过"方差限制).然后,当然,您可以拥有同一事件类型的多个订阅者; 所以你需要一些东西来将事件路由到多个订阅者.这通常被称为多路复用器,它将是一个专门化的IEventPublisher.每个事件类型需要一个多路复用器 - 这将使用更窄的.用于给定事件的多路复用器取决于类型,因此多路复用器的集合将由字典管理,因此您可以按类型查找它们.然后,要按类型向多个订阅者发布事件,只需查找多路复用器并调用其IEventPublisher.Publish方法即可.管理多路复用器的东西是一种类型,IEventPublisher通常称为Dispatcher(有些人可能称之为路由器).
例如:
public class NarrowingSubscriber<TBase, TDerived> : IEventSubscriber<TBase>
where TDerived : TBase
where TBase : IEvent
{
private IEventSubscriber<TDerived> inner;
public NarrowingSubscriber(IEventSubscriber<TDerived> inner)
{
if (inner == null) throw new ArgumentNullException("inner");
this.inner = inner;
}
public void AttachSubscriber(IEventSubscriber<TDerived> subscriber)
{
inner = subscriber;
}
public void Handle(TBase data)
{
inner.Handle((TDerived)data);
}
}
public class Multiplexor<T> : IEventSubscriber<T> where T : IEvent
{
private readonly List<IEventSubscriber<T>> subscribers =
new List<IEventSubscriber<T>>();
public void AttachSubscriber(IEventSubscriber<T> subscriber)
{
subscribers.Add(subscriber);
}
public void RemoveSubscriber(IEventSubscriber<T> subscriber)
{
subscribers.Remove(subscriber);
}
public void Handle(T data)
{
subscribers.ForEach(x => x.Handle(data));
}
}
public class Dispatcher<TBase> : IEventPublisher<TBase> where TBase : IEvent
{
private readonly Dictionary<Type, Multiplexor<TBase>> subscriptions =
new Dictionary<Type, Multiplexor<TBase>>();
public void Publish(TBase data)
{
Multiplexor<TBase> multiplexor;
if (subscriptions.TryGetValue(data.GetType(), out multiplexor))
{
multiplexor.Handle(data);
}
}
public void Subscribe<TEvent>(IEventSubscriber<TEvent> handler)
where TEvent : TBase
{
Multiplexor<TBase> multiplexor;
if (!subscriptions.TryGetValue(typeof(TEvent), out multiplexor))
{
multiplexor = new Multiplexor<TBase>();
subscriptions.Add(typeof(TEvent), multiplexor);
}
multiplexor.AttachSubscriber(new NarrowingSubscriber<TBase, TEvent>(handler));
}
}
Run Code Online (Sandbox Code Playgroud)
所以,你基本上发布了4次.一旦到达调度员,一次到多路复用器,一次到较窄,一次到非基础设施用户.如果您有两个订阅者(EventOneEventSubscriber和EventTwoEventSubscriber)订阅了两种类型的事件(EventOne和EventTwo),那么您可以创建一个调度程序并发布如下事件:
var d = new Dispatcher<IEvent>();
var eventTwoSubscriber = new EventTwoEventSubscriber();
d.Subscribe(eventTwoSubscriber);
var eventOneSubscriber = new EventOneEventSubscriber();
d.Subscribe(eventOneSubscriber);
d.Publish(new EventOne());
d.Publish(new EventTwo());
Run Code Online (Sandbox Code Playgroud)
当然,事件将来自IEvent:
public class EventOne : IEvent
{
}
public class EventTwo : IEvent
{
}
Run Code Online (Sandbox Code Playgroud)
此特定限制不会多次考虑调度事件.例如,我可以订阅订阅者EventOne和订阅者IEvent.EventOne如果EventOne通过调度程序发布对象,则此实现仅发布给订阅者- 它不会发布给IEvent订阅者.我会把它作为读者的练习:)
如果你希望做的是自动连接订阅者而不必构建它们(我没有看到很多价值,考虑一个组合根)然后你可以添加一个相当简单的方法Dispatcher(或其他地方,如果需要)订阅所有兼容订户:
public void InitializeSubscribers()
{
foreach (object subscriber in
from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where !type.IsAbstract && type.IsClass && !type.ContainsGenericParameters &&
type.GetInterfaces().Any(t => t.IsGenericType &&
t.GetGenericTypeDefinition() == typeof (IEventSubscriber<>))
select type.GetConstructor(new Type[0])
into constructorInfo
where constructorInfo != null
select constructorInfo.Invoke(new object[0]))
{
Subscribe((dynamic) subscriber);
}
}
Run Code Online (Sandbox Code Playgroud)
你使用如下:
var d = new Dispatcher<IEvent>();
d.InitializeSubscribers();
Run Code Online (Sandbox Code Playgroud)
... Dispatcher<T>如果需要,使用容器来解析对象.
| 归档时间: |
|
| 查看次数: |
1707 次 |
| 最近记录: |