2016-07-05 2 views
3

Say У меня есть два класса, которые инстанцированы на замке Виндзор, и каждый из них имеет зависимость от того же интерфейса:Override какого компонента реализация вводится для вложенных/транзитивных зависимостей

  • FooRepoIApiClient
  • BarRepoIApiClient

в этом случае IApiClient осуществляется одним классом, GenericApiClient, который знает, как общаться с любым API. Тем не менее, я хочу, чтобы создать различные экземпляры GenericApiClient, которые передаются различные значения конфигурации (открытые с помощью IApiClientConfiguration), так что FooRepo разговаривает с API конечной Foo и BarRepo разговаривает с Bar API конечной точки:

  • FooRepoIApiClient (GenericApiClient)IApiClientConfiguration (FooClientConfiguration)
  • BarRepoIApiClient (GenericApiClient)IApiClientConfiguration (BarClientConfiguration)

Вот что я пытался до сих пор:

container = new WindsorContainer(); 
container.Register(
    Component.For<HomeController>().LifeStyle.Transient, 
    Component.For<FooRepo>() 
     .LifeStyle.Transient, 
    Component.For<BarRepo>() 
     //.DependsOn(Dependency.OnComponent<IApiClientConfiguration, BarClientConfiguration>()) // this does nothing cause the client config is not a direct dependency :(
     .LifeStyle.Transient, 
    Component.For<IApiClient>() 
     .ImplementedBy<GenericApiClient>() 
     //.DependsOn(Dependency.OnComponent<IApiClientConfiguration, BarClientConfiguration>()) // this overrides for both FooRepo and BarRepo :(
     .LifeStyle.Transient, 
    Component.For<IApiClientConfiguration>() 
     .ImplementedBy<FooClientConfiguration>() 
     .LifeStyle.Transient, 
    Component.For<IApiClientConfiguration>() 
     .ImplementedBy<BarClientConfiguration>() 
     .LifeStyle.Transient); 

У меня возникли проблемы, выяснить, как получить FooRepo, чтобы получить экземпляр GenericApiClient сконфигурированный с FooClientConfiguration с BarRepo получать BarClientConfiguration:

  • По умолчанию они оба получают FooClientConfiguration, так это то, что зарегистрировано сначала
  • Я могу переопределить конфигурацию для IApiClient с использованием DependsOn(...), но это относится как к FooRepo, так и к BarRepo
  • Если я использую DependsOn(...) на BarRepo, это не имеет никакого эффекта

(В случае выше вопрос не ясно, у меня есть как минимум рабочего примера here)

Есть ли способ я могу настроить замок Виндзор делать то, что я хочу? Есть ли способ улучшить структуру моего кода, чтобы у меня не было этой проблемы?

ответ

4
container.Register(
    Component.For<IApiClientConfiguration>() 
     .ImplementedBy<FooClientConfiguration>() 
     .Named("FooConfiguration") 
     .LifestyleTransient(), 
    Component.For<IApiClientConfiguration>() 
     .ImplementedBy<BarClientConfiguration>() 
     .Named("BarConfiguration") 
     .LifestyleTransient(), 
    Component.For<IApiClient, GenericApiClient>() 
     .Named("FooClient") 
     .DependsOn(Dependency.OnComponent(
      typeof(IApiClientConfiguration), "FooConfiguration")), 
    Component.For<IApiClient, GenericApiClient>() 
     .Named("BarClient") 
     .DependsOn(Dependency.OnComponent(
      typeof(IApiClientConfiguration), "BarConfiguration")), 
    Component.For<FooRepo>() 
     .DependsOn(Dependency.OnComponent(typeof(IApiClient), "FooClient")), 
    Component.For<BarRepo>() 
     .DependsOn(Dependency.OnComponent(typeof(IApiClient), "BarClient")) 
    ); 

Это некрасиво. И может быть немного упрощен синтаксис. Виндзор обычно предлагает несколько разных способов сделать все.

Вы определяете две различные реализации GenericApiClient, и для каждого вы указываете, какую конфигурацию использовать. Затем при регистрации FooRepo и BarRepo для каждого из них вы указываете, какую именованную реализацию IApiClient использовать.

Если тот или другой по умолчанию, вы можете указать его, используя IsDefault() и только имя другого.Она также может быть легче следовать, если вы пишете отдельные инсталляторы для отдельных групп реализаций, или даже просто методы в одной и той же установки, как

RegisterFooDependencies(IWindsorContainer container) 
{ 
    container.Register(
     Component.For<IApiClientConfiguration>() 
      .ImplementedBy<FooClientConfiguration>() 
      .Named("FooConfiguration") 
      .LifestyleTransient(), 
     Component.For<IApiClient, GenericApiClient>() 
      .Named("FooClient") 
      .DependsOn(Dependency.OnComponent(
       typeof(IApiClientConfiguration), "FooConfiguration")), 
     Component.For<FooRepo>() 
      .DependsOn(Dependency.OnComponent(typeof(IApiClient), "FooClient"))   
    ); 
} 
+0

Прохладный, это имеет смысл. Создайте различные именованные компоненты для 'IApiClient' и добавьте переопределение для каждой именованной версии. Интересно, есть ли способ сделать это, не называя компоненты «IApiClientConfiguration». Постскриптум Я должен был внести небольшое изменение, чтобы заставить ваш код работать ... надеюсь, вы не против, что я его отредактировал. –

+0

Совсем нет. Я читал больше, потому что не хотел бы отказываться от надежды найти более простой способ. Но я ничего не понял. –

+0

FYI [это упрощение] (https://github.com/mkropat/WindsorTransitiveDependencyOverrideMWE/commit/68fdab3c4621c26bd45636b108c4c4c0a65f1c06), похоже, работает на мой случай. –

1

Ответ @ Скотт-Ханнена дал это технически правильно и он тоже правильно, когда он говорит

Это некрасиво.

Windsor очень гибкий, и вы можете с достаточной конфигурацией и расширениями сделать это много, но должны ли вы?

Обычно, если все становится слишком сложным, и вы обнаруживаете, что у вас проблемы с объяснением инструмента, как ваша архитектура объединена, возможно, это слишком сложно объяснить и поддерживать люди?

У вас есть два набора абстракций, которые всегда идут вместе (ваши Repo и IApiClientConfiguration), разделенные другой абстракцией IApiClient.

Если они идут руку об руке, я бы поставил их бок о боке, и переставить цепь зависимостей таким образом, что вы в конечный итоге с чем-то похожим на:

public FooRepo(IApiClient client, FooClientConfiguration clientConfig) 
{ 
    // ...stuff, and then 
    client.Configure(config); 
} 

Теперь вы делаете отношения явными и очевидными любому человеку, читающему ваш код. И, в качестве побочной выгоды, ваша конфигурация Windsor станет тривиальной.

+0

Ницца. Для небольшой сложности класса вы можете значительно упростить сложность Windsor. Это похоже на хороший компромисс. –

+0

Что еще более важно, вы делаете отношения в своем коде более ясными, и ваш код легче следовать –

+0

Я согласен с тем, что мой ответ ввел кучу уродливой сложности. Я отвечал буквально и не видел леса для деревьев. Но я думаю, что если 1) 'FooRepo' зависит от чего-то, что также требует конфигурации клиента, и 2)' FooRepo' отвечает за каким-то настройку этой зависимости, тогда 'IApiClient' на самом деле не является хорошей абстракцией. Его нельзя заменить чем-то, что * не является клиентом API и не нуждается в этой конфигурации. 'FooRepo' не должен знать или заботиться о зависимостях своих зависимостей. –

1

Я прочитал ответ от @ Krzysztof Kozmic. Я согласен с точкой - мой принятый ответ, хотя, технически корректный, требует некоторой сложной конфигурации.

Я не уверен, что согласен с тем, что класс настраивает собственные зависимости. Если мы делаем это:

public FooRepo(IApiClient client, FooClientConfiguration clientConfig) 
{ 
    // ...stuff, and then 
    client.Configure(config); 
} 

затем FooRepo слишком много знает о IApiClient. Он знает, что для этого требуется конфигурация клиента. Это меньше абстракции. Я не указал на это в своем первоначальном ответе, но почему бы FooRepo даже знать, что от него зависит клиент API вообще, не говоря уже о клиенте API, который требует конфигурации?

Таким образом, хотя мой первый ответ был буквальным - как сделать Виндзор делать то, что было предложено - вот лучший способ решить исходную задачу:

Во-первых, не делают FooRepo или BarRepo зависит от IApiClient. Они не должны ничего знать о клиентах API. Они просто зависят от того, что предоставляет услугу. Возможно, что-то вроде этого:

public interface IFooService 

Тогда реализация такова:

public class FooApiClient : GenericApiClient, IFooService 
{ 
    public FooApiClient() : base(new FooClientConfiguration()) 
    {} 

    // Whatever IFooService does  
} 

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

FooApiClient связан с FooClientConfiguration (что мы обычно стараемся избегать с помощью DI), но это его цель - обеспечить эту связанную конфигурацию, чтобы она не существовала ни в конфигурации DI, ни в классах, которые зависят в теме.

Теперь, когда грязный материал перемещается из конфигурации DI и FooRepo еще проще, потому что это в зависимости от правды абстракции, а не то, которое имеет I перед ним, но на самом деле представляет собой конкретную реализацию (АНИ клиент.)

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