覆盖为嵌套/传递依赖项注入的组件实现

时间:2016-07-05 21:50:39

标签: c# .net castle-windsor ioc-container

说我有两个由Castle Windsor实例化的类,每个类都依赖于同一个接口:

  • FooRepoIApiClient
  • BarRepoIApiClient

在这种情况下,IApiClient由一个知道如何与任何API通信的类GenericApiClient实现。但是,我想创建GenericApiClient的不同实例,这些实例传递不同的配置值(通过IApiClientConfiguration公开),以便FooRepo与Foo API端点和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(...)的配置,但这适用于FooRepoBarRepo
  • 如果我在DependsOn(...)上使用BarRepo,则无效

(如果上述问题不明确,我有一个最低工作示例here

我有什么方法可以让Castle Windsor按照我的意愿去做吗?有没有办法更好地构建我的代码,所以我没有这个问题?

3 个答案:

答案 0 :(得分: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的两个不同实现,并且每个实现都指定要使用的配置。然后,在注册FooRepoBarRepo时,为每个人指定要使用的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"))          
    );
}

答案 1 :(得分:1)

@ Scott-Hannen给出的答案技术上正确,当他说

时他也是正确的
  

它不漂亮。

温莎非常灵活,你可以通过足够的配置和扩展来做很多事,但是你应该这样做吗?

通常,如果事情变得太复杂,你发现自己在向工具解释你的架构是如何组合在一起时遇到问题,那么解释和维护人类可能会过于复杂?

您有两组抽象,它们总是在一起(您的RepoIApiClientConfiguration)由另一个抽象IApiClient隔开。

如果它们齐头并进,我会将它们并排放置,并重新排列依赖关系链,以便最终得到类似的内容:

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

现在,您正在使任何阅读代码的人明白且明显地建立关系。而且,作为一个附带好处,您的Windsor配置也将变得微不足道。

答案 2 :(得分:1)

我从@Krzysztof Kozmic那里读到了答案。我同意这一点 - 我接受的答案,虽然技术上正确,但需要一些复杂的配置。

我不确定我是否同意让一个类配置自己的依赖项。如果我们这样做:

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

然后FooRepoIApiClient了解太多。它知道它需要客户端配置。它不是一种抽象。我没有在最初的答案中指出这一点,但为什么FooRepo甚至应该知道它依赖的是API客户端,更不用说需要配置的API客户端?

因此,虽然我的第一个答案是字面意思 - 如何让温莎做出被问到的问题 - 这是解决原始问题的更好方法:

首先,不要让FooRepoBarRepo依赖于IApiClient。他们不应该对API客户端有任何了解。他们只是依赖某种提供服务的东西,不知何故。也许是这样的:

public interface IFooService

然后实现如下:

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

    // Whatever IFooService does    
}

我厌倦了使用依赖注入执行所有。但在这种情况下,为了便于阅读,我们可以创建一个用给定配置组成API客户端的类。

FooApiClientFooClientConfiguration耦合(我们通常试图通过DI避免这种情况),但这就是它的目的 - 提供耦合配置,使其不存在于任何一个DI配置或依赖它的类。

现在凌乱的东西被移出DI配置,FooRepo甚至更简单,因为它取决于 true 抽象,而不是I in它的前面但实际上代表了一个具体的实现(API客户端。)