覆盖为 nested/transitive 依赖项注入的组件实现

Override what component implementation is injected for nested/transitive dependencies

假设我有两个由 Castle Windsor 实例化的 classes,并且每个都依赖于同一接口:

在这种情况下,IApiClient 由知道如何与任何 API 通信的 class、GenericApiClient 实现。但是,我想创建 GenericApiClient 的不同实例,这些实例传递不同的配置值(通过 IApiClientConfiguration 公开),以便 FooRepo 与 Foo API 端点和 BarRepo 与酒吧对话 API 端点:

这是我到目前为止尝试过的方法:

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 获得配置了 FooClientConfigurationGenericApiClient 实例,BarRepo 获得 [=32] =]:

(如果上面的问题不清楚,我有一个最小的工作示例here

有什么方法可以配置 Castle Windsor 来做我想做的事吗?有没有什么方法可以更好地构建我的代码,这样我就不会遇到这个问题?

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"))          
    );
}

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

也是正确的

It's not pretty.

Windsor 非常灵活,您可以通过足够的配置和扩展让它做很多事情,但是您应该这样做吗?

通常,如果事情变得太复杂,并且您发现自己无法向工具解释您的架构是如何组合在一起的,也许它太复杂而无法向人类解释和维护?

你有两组抽象,它们总是在一起(你的 RepoIApiClientConfiguration)被另一个抽象分开 IApiClient

如果它们齐头并进,我会将它们并排放置,并重新排列依赖链,最终得到类似于:

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

现在,您可以让阅读您代码的任何人都清楚明白地了解这种关系。而且,作为附带好处,您的 Windsor 配置也将变得微不足道。

我阅读了@Krzysztof Kozmic 的回答。我同意这一点——我接受的答案虽然在技术上是正确的,但需要一些复杂的配置。

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

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    
}

我对使用依赖注入做一切感到迷惑。但在这种情况下,为了可读性,我们可以只创建一个 class 组成一个具有给定配置的 API 客户端。

FooApiClient 耦合到 FooClientConfiguration(我们通常使用 DI 试图避免的那种事情)但这就是它的目的 - 提供耦合配置,以便它不存在于DI 配置或依赖于它的 classes。

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