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);
Другие вопросы по тегам