SimpleInjector HowTo Регистрация нескольких открытых универсальных интерфейсов для единой универсальной реализации
Я пытаюсь начать с SimpleInjector в качестве контейнера IOC, и до сих пор я очень доволен этим. Но сейчас я застрял на проблеме, которую не могу решить. Я искал на SO и в документации, но, кажется, еще не ответил. Я видел документ с практическими рекомендациями от SimpleInjector, но он не охватывает открытые универсальные интерфейсы.
У меня есть два общих интерфейса, как эти:
public interface IEventPublisher<TEvent>
{
void Publish(TEvent Event);
}
public interface IEventSubscriber<TEvent>
{
void Subscribe(Action<TEvent> CallBack);
}
И одна открытая универсальная реализация для этих двух:
class EventMediator<T> : IEventPublisher<T>, IEventSubscriber<T>
{
List<Action<T>> Subscriptions = new List<Action<T>>();
public void Publish(T Event)
{
foreach (var Subscription in this.Subscriptions)
Subscription.Invoke(Event);
}
public void Subscribe(Action<T> CallBack)
{
this.Subscriptions.Add(CallBack);
}
}
В моем приложении я настраиваю SimpleInjector следующим образом:
this.Container = new SimpleInjector.Container();
this.Container.RegisterOpenGeneric(typeof(IEventPublisher<>), typeof(EventMediator<>), Lifestyle.Singleton);
this.Container.RegisterOpenGeneric(typeof(IEventSubscriber<>), typeof(EventMediator<>), Lifestyle.Singleton);
this.Container.Verify();
Я пытаюсь архивировать: я хотел бы получить точно такой же экземпляр при запросе IEventPublisher или IEventSubscriber. И, кроме того, этот экземпляр должен быть единственным для любого T.
Я проверил это с этими строками:
class DummyEvent {}
var p = this.Container.GetInstance<IEventPublisher<DummyEvent>>();
var s = this.Container.GetInstance<IEventSubscriber<DummyEvent>>();
var areSame = (object.ReferenceEquals(p,s));
К сожалению, p и s не относятся к одному и тому же экземпляру. Кто-нибудь знает решение этой проблемы?
2 ответа
Для этого есть определенные решения, вот одно: создайте отдельные реализации для IEventPublisher<T>
а также IEventSubscriber<T>
и пусть они делегируют EventMediator<T>
, Например, с этими реализациями:
public class EventPublisher<TEvent> : IEventPublisher<TEvent>
{
private readonly EventMediator<TEvent> mediator;
public EventPublisher(EventMediator<TEvent> mediator) {
this.mediator = mediator;
}
public void Publish(TEvent Event) {
this.mediator.Publish(Event);
}
}
public class EventSubscriber<TEvent> : IEventSubscriber<TEvent>
{
private readonly EventMediator<TEvent> mediator;
public EventSubscriber(EventMediator<TEvent> mediator) {
this.mediator = mediator;
}
public void Subscribe(Action<TEvent> CallBack) {
this.mediator.Subscribe(Callback);
}
}
Теперь вы делаете регистрацию следующим образом:
container.RegisterSingleOpenGeneric(typeof(EventMediator<>), typeof(EventMediator<>));
container.RegisterSingleOpenGeneric(typeof(IEventPublisher<>), typeof(EventPublisher<>));
container.RegisterSingleOpenGeneric(typeof(IEventSubscriber<>), typeof(EventSubscriber<>));
Теперь оба EventPublisher<DummyEvent>
а также EventSubscriber<DummyEvent>
будет указывать на то же EventMediator<DummyEvent>
пример.
Другой способ достичь этого без дополнительного типа - использовать ResolveUnregisteredType
событие (которое является то, что RegisterOpenGeneric
сам метод расширения использует под одеялом). Ваша конфигурация будет выглядеть так:
container.RegisterSingleOpenGeneric(typeof(EventMediator<>), typeof(EventMediator<>));
container.ResolveUnregisteredType += (s, e) =>
{
if (e.UnregisteredServiceType.IsGenericType)
{
var def = e.UnregisteredServiceType.GetGenericTypeDefinition();
if (def == typeof(IEventPublisher<>) || def == typeof(IEventSubscriber<>))
{
var mediatorType = typeof(EventMediator<>)
.MakeGenericType(e.UnregisteredServiceType.GetGenericArguments()[0]);
var producer = container.GetRegistration(mediatorType, true);
e.Register(producer.Registration);
}
}
};
Вы даже можете извлечь этот код в более общий метод расширения. Таким образом, ваша регистрация будет выглядеть так:
container.RegisterSingleOpenGeneric(typeof(EventMediator<>), typeof(EventMediator<>));
container.ForwardOpenGenericTo(typeof(IEventPublisher<>), typeof(EventMediator<>));
container.ForwardOpenGenericTo(typeof(IEventSubscriber<>), typeof(EventMediator<>));
Метод расширения будет выглядеть так:
public static void ForwardOpenGenericTo(this Container container,
Type openGenericServiceType, Type openGenericServiceTypeToForwardTo)
{
container.ResolveUnregisteredType += (s, e) =>
{
var type = e.UnregisteredServiceType;
if (type.IsGenericType)
{
if (type.GetGenericTypeDefinition() == openGenericServiceType)
{
var forwardToType = openGenericServiceTypeToForwardTo.MakeGenericType(
type.GetGenericArguments());
var producer = container.GetRegistration(forwardToType, true);
e.Register(producer.Registration);
}
}
};
}
Вы регистрируетесь IEventPublisher
а также IEventSubscriber
как отдельные синглтоны. Вам нужно будет так или иначе рефакторинг вашего кода. Одним из решений является разделение 3 обязанностей вашего посредника:
Подписчик
public interface IEventSubscriber<TEvent>
{
void Subscribe(Action<TEvent> CallBack);
}
public class EventSubscriber<T> : IEventSubscriber<T>
{
public readonly ISubscriptions<T> subscriptions;
public EventSubscriber(ISubscriptions<T> subscriptions)
{
this.subscriptions = subscriptions;
}
public void Subscribe(Action<T> CallBack)
{
this.subscriptions.Add(CallBack);
}
}
Издатель
public interface IEventPublisher<TEvent>
{
void Publish(TEvent Event);
}
public class EventPublisher<T> : IEventPublisher<T>
{
public readonly ISubscriptions<T> subscriptions;
public EventPublisher(ISubscriptions<T> subscriptions)
{
this.subscriptions = subscriptions;
}
public void Publish(T Event)
{
foreach (var subscription in this.subscriptions)
{
subscription.Invoke(Event);
}
}
}
Подписки
public interface ISubscriptions<T> : IList<Action<T>> { }
public class Subscriptions<T> : List<Action<T>>, ISubscriptions<T> { }
Только подписки должны быть зарегистрированы как синглтон
var container = new Container();
container.RegisterOpenGeneric(typeof(IEventSubscriber<>), typeof(EventSubscriber<>));
container.RegisterOpenGeneric(typeof(IEventPublisher<>), typeof(EventPublisher<>));
container.RegisterSingleOpenGeneric(typeof(ISubscriptions<>), typeof(Subscriptions<>));
container.Verify();
var p = container.GetInstance<IEventPublisher<DummyEvent>>();
var s = container.GetInstance<IEventSubscriber<DummyEvent>>();
Assert.That(
(p as EventPublisher<DummyEvent>).subscriptions ==
(s as EventSubscriber<DummyEvent>).subscriptions);