2016-04-27 1 views
3

Так что это привлечет меня к тому, чтобы показать большую часть моей сантехники, но я постараюсь сохранить ее на минимальном минимуме, чтобы этот вопрос был простым.Как я могу разрешить декораторы для объектов, созданных фабрикой в ​​Simple Injector

Одна из моих конечных точек API полагается на внешних поставщиков для завершения вызова. Когда пользователь отправляет запрос на эту конечную точку, он может указать, какой провайдер они хотят использовать для обработки запроса, скажем, провайдеры: Bing и Google.

Так у меня есть IProvider интерфейса и два конкретных реализации BingProvider и GoogleProvider (в моем реальном API интерфейс поставщика на самом деле это общий интерфейс, но я уезжаю на дженерики, чтобы избежать этого грязнее, чем это должно быть). Мне нужно разрешить правильный поставщик на основе поля в запросе. Simple Injector не позволяет регистрировать несколько конкретных реализаций одного и того же интерфейса, поэтому я должен использовать фабрику; Я создаю один, который выглядит примерно так:

public class ProviderFactory 
{ 
    private readonly Func<string, IProvider> _Selector; 

    public ProviderFactory(Func<string, IProvider> selector) 
    { 
     this._Selector = selector; 
    } 

    public IProvider Get(string provider) 
    { 
     return this._Selector(provider); 
    } 
} 

зарегистрировать свою фабрику с контейнером, делая что-то вроде этого:

container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider => 
    { 
     switch (provider) 
     { 
      case "Bing": 
       return container.GetInstance<BingProvider>() 
      case "Google": 
       return container.GetInstance<GoogleProvider>() 
      default: 
       throw new ArgumentOutOfRangeException("Unknown provider: " + provider); 
     } 
    })); 

я проверить его. Оно работает. Фантастика.

Теперь мне нужно создать и зарегистрировать несколько декораторов для моего IProvider. Каждая конкретная реализация IProvider должна иметь эти декораторы, когда контейнер разрешает их. Для этого примера можно сказать, что у меня есть Decorator1 и Decorator2, которые реализуют IProvider. Я зарегистрирую их в контейнере следующим образом:

container.RegisterDecorator(typeof(IProvider), typeof(Decorator1), Lifestyle.Singleton); 
container.RegisterDecorator(typeof(IProvider), typeof(Decorator2), Lifestyle.Singleton); 

В этом проблема. Когда моя фабрика разрешает экземпляр BingProvider или GoogleProvider, это именно то, что я получаю. Я хочу получить экземпляр Decorator2, который украшает экземпляр Decorator1, который, в свою очередь, украшает любые конкретные реализации IProvider, которые я просил. Я предполагаю, что это потому, что я специально не прошу контейнер разрешить экземпляр IProvider, но я прошу его разрешить конкретную реализацию IProvider.

Кажется, что я все запутался здесь, и я не уверен, что лучший способ - разрешить его.

Редактировать

Хорошо, после того, как думать об этом еще немного, я понимаю, почему декораторы никогда не будет решена. Возьмите, например, Decorator1 и BingProvider. Оба Decorator1 и BingProvider реализуют IProvider, но Decorator1 не реализует BingProvider, поэтому, когда я прошу Simple Injector разрешить экземпляр BingProvider, он даже не может дать мне экземпляр Decorator1. Мне нужно попросить его как-то разрешить экземпляр IProvider, но дайте мне правильную конкретную реализацию с декораторами на месте.

Отлично, что я понимаю, но теперь я менее уверен в продолжении.

Update

На основании ответа Стивена я изменил мой завод регистрации, как это:

var bingProvider = Lifestyle.Singleton 
    .CreateProducer<IProvider, BingProvider>(container); 

var googleProvider = Lifestyle.Singleton 
    .CreateProducer<IProvider, GoogleProvider>(container); 

container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider => 
    { 
     switch (provider) 
     { 
      case "Bing": 
       return bingProvider.GetInstance(); 
      case "Google": 
       return googleProvider.GetInstance(); 
      default: 
       throw new ArgumentOutOfRangeException("Unknown provider: " + provider); 
     } 
    })); 

Моя новая проблема заключается в том, что, когда я бегу мой модульного тестирования для проверки контейнера тест завершается с (я должен был обработать это сообщение об ошибке, чтобы оно соответствовало моему примеру здесь, надеюсь, что это не приведет к утере перевода):

Конфигурация недействительна. Сообщалось о следующих диагностических предупреждениях:
- [Torn Lifestyle] Регистрация для IProvider карт с той же реализацией и образом жизни, что и регистрация для IProvider. Оба они сопоставляются с Decorator1 (Singleton). Это приведет к тому, что каждая регистрация будет разрешена для другого экземпляра: каждая регистрация будет иметь свой собственный экземпляр.
- [Torn Lifestyle] Регистрация для IProvider карт с той же реализацией и образом жизни, что и регистрация для IProvider. Оба они сопоставляются с Decorator2 (Singleton). Это приведет к тому, что каждая регистрация будет разрешена для другого экземпляра: каждая регистрация будет иметь свой собственный экземпляр.
Дополнительную информацию о предупреждениях см. В описании свойства Error. Пожалуйста, см. https://simpleinjector.org/diagnostics, как устранить проблемы и как подавить отдельные предупреждения.

+0

Я не знаю много про простой инжектор. Но такая ситуация, которая у вас есть (несколько реализаций одного и того же интерфейса), является примером того, почему [Pure DI] (http://blog.ploeh.dk/2014/06/10/pure-di/) лучше, чем с использованием контейнера DI, на мой взгляд. Взгляните на [эту статью] (http://criticalsoftwareblog.com/index.php/2015/08/23/why-di-container-fail-with-complex-object-graphs /) для получения дополнительной информации об этом аргументе. –

ответ

5

Простой Injector не позволяет регистрировать несколько конкретных реализаций одного и того же интерфейса

Это утверждение неверно. На самом деле существует несколько способов сделать это. Я думаю, что три наиболее распространенных способов сделать это являются:

  1. Использование условных регистрации
  2. зарегистрировать коллекцию типов
  3. вручную создать InstanceProducer экземпляров.

Особенно вариант 1 и 3, кажется, наиболее подходит в вашем случае, так что давайте начнем с вариантом 3: Создание InstanceProducers:

// Create two providers for IProvider according to the required lifestyle. 
var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container); 
var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container); 

container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider => { 
    switch (provider) { 
     case "Bing": return bing.GetInstance(); 
     case "Google": return google.GetInstance(); 
     default: throw new ArgumentOutOfRangeException(); 
    } 
})); 

Здесь мы создаем два InstanceProducer экземпляров, по одному для каждого IProvider. Важная часть здесь - создать производителя для абстракции IProvider, поскольку это позволяет применять декораторы для IProvider.

В качестве альтернативы вы можете переместить инструкцию switch - case внутри ProviderFactory и предоставить ее двум отдельным делегатам; по одному для каждого провайдера.Например:

public ProviderFactory(Func<IProvider> bingProvider, Func<IProvider> googleProvider) { .. } 

public IProvider Get(string provider) { 
    switch (provider) { 
     case "Bing": return bingProvider(); 
     case "Google": return googleProvider(); 
     default: throw new ArgumentOutOfRangeException(); 
    } 
} 

регистрация выглядит очень похожа на предыдущий:

var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container); 
var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container); 

container.RegisterSingleton<ProviderFactory>(new ProviderFactory(
    bingProvider: bing.GetInstance, 
    googleProvider: google.GetInstance)); 

Вместо введение Func<T> делегатов на завод, в зависимости от ваших потребностей он может работать для вас, чтобы ввести IProvider непосредственно. Это означает, что ваш конструктор будет выглядеть следующим образом:

public ProviderFactory(IProvider bing, IProvider google) { ... } 

Теперь вы можете использовать условные регистрации на IProvider для устранения неоднозначности на аргументах конструктора:

container.RegisterSingleton<ProviderFactory>(); 
container.RegisterConditional<IProvider, BingProvider>(
    c => c.Consumer.Target.Name == "bing"); 
container.RegisterConditional<IProvider, GoogleProvider>(
    c => c.Consumer.Target.Name == "google"); 

Преимущество этого в том, что вы не откладывайте построение графика объекта; все поставщики вводятся непосредственно, когда потребитель завода решается.

В качестве альтернативы вы также можете поэкспериментировать с конструкцией, в которой вы заменяете фабрику абстракцией диспетчера. Фабричные абстракции часто не являются самым простым решением для потребителя, потому что теперь им приходится иметь дело как с заводским типом, так и с возвратом служебной абстракции. С другой стороны, диспетчер или процессор дает потребителю одну абстракцию. Это делает потребитель (и его модульные тесты) более простым.

Такой диспетчер будет очень похож на интерфейс IProvider, но добавляет параметр string provider к его методам экземпляра. Например:

interface IProviderDispatcher { 
    void DoSomething(string provider, ProviderData data); 
} 

Если реализация диспетчера может выглядеть следующим образом:

public ProviderDispatcher(IProvider bing, IProvider google) { .. } 

public void DoSomething(string provider, ProviderData data) { 
    this.Get(provider).DoSomething(data); 
} 

private IProvider Get(string provider) { 
    switch (provider) { 
     case "Bing": return this.bing; 
     case "Google": return this.google; 
     default: throw new ArgumentOutOfRangeException(); 
    } 
} 

Раствор для диспетчера может быть таким же афом для завода, но теперь мы скрыть дополнительный шаг от потребителя ,

Еще лучше, если мы полностью удалим абстракцию IProviderDispatcher, но это возможно только в том случае, если данные времени выполнения string provider являются контекстуальными данными, доступными во время запроса. В этом случае мы могли бы сделать следующее:

interface IProviderContext { 
    string CurrentProvider { get; } 
} 

И вместо отдельной абстракции поставщика, мы можем иметь реализацию прокси на IProvider:

class ProviderDispatcherProxy : IProvider { 
    public ProviderDispatcherProxy(Func<IProvider> bingProvider, 
     Func<IProvider> googleProvider, 
     IProviderContext providerContext) { ... } 

    void IProvider.DoSomething(ProviderData data) { 
     // Dispatch to the correct provider 
     this.GetCurrentProvider.DoSomething(data); 
    } 

    private IProvider GetCurrentProvider() => 
     switch (this.providerContext.CurrentProvider) { 
      case "Bing": return this.bingProvider(); 
      case "Google": return this.googleProvider(); 
      default: throw new ArgumentOutOfRangeException(); 
     } 
    }; 
} 

class AspNetProviderContext : IProviderContext { 
    public CurrentProvider => HttpContext.Current.Request.QueryString["provider"]; 
} 

Опять же, внутренне он все еще так же, как раньше, но теперь, поскольку ценность поставщика - это то, что мы можем разрешить из имеющегося контекста окружающей среды (HttpContext.Current), мы сможем разрешить потребителю напрямую работать с IProvider. Вы можете зарегистрировать это следующим образом:

container.RegisterSingleton<IProviderContext>(new AspNetProviderContext()); 

var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container); 
var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container); 
container.RegisterSingleton<IProvider>(new ProviderDispatcherProxy(
    bingProvider: bing.GetInstance, 
    googleProvider: google.GetInstance)); 

Теперь вы можете просто вводить в IProvider в ваших потребителей и диспетчеризация автоматически произойдет для вас на заднем плане.

+0

Там есть что переварить. Я пошел с ручным созданием поставщиков экземпляров, потому что он запустил меня и работал быстрее всех. Теперь я пытаюсь разобраться с проблемой _Torn Lifestyle_, вызываемой декораторами. –

+0

@JasonBoyd вы можете обновить свой вопрос и показать свои регистрации и точное сообщение, которое вы получаете? – Steven

+0

Пожалуйста, ознакомьтесь с обновленным вопросом –

Смежные вопросы