覆盖为 nested/transitive 依赖项注入的组件实现
Override what component implementation is injected for nested/transitive dependencies
假设我有两个由 Castle Windsor 实例化的 classes,并且每个都依赖于同一接口:
FooRepo
→IApiClient
BarRepo
→IApiClient
在这种情况下,IApiClient
由知道如何与任何 API 通信的 class、GenericApiClient
实现。但是,我想创建 GenericApiClient
的不同实例,这些实例传递不同的配置值(通过 IApiClientConfiguration
公开),以便 FooRepo
与 Foo API 端点和 BarRepo
与酒吧对话 API 端点:
FooRepo
→IApiClient (GenericApiClient)
→IApiClientConfiguration (FooClientConfiguration)
BarRepo
→IApiClient (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
获得配置了 FooClientConfiguration
的 GenericApiClient
实例,BarRepo
获得 [=32] =]:
- 默认情况下,它们都得到
FooClientConfiguration
,因为这是最先注册的
- 我可以使用
DependsOn(...)
覆盖 IApiClient
的配置,但这适用于 FooRepo
和 BarRepo
- 如果我在
BarRepo
上使用DependsOn(...)
,它没有效果
(如果上面的问题不清楚,我有一个最小的工作示例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
的两个不同实现,并且您正在为每个指定要使用的配置。然后,在注册 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"))
);
}
@Scott-Hannen 给出的答案技术上是正确的,他说
也是正确的
It's not pretty.
Windsor 非常灵活,您可以通过足够的配置和扩展让它做很多事情,但是您应该这样做吗?
通常,如果事情变得太复杂,并且您发现自己无法向工具解释您的架构是如何组合在一起的,也许它太复杂而无法向人类解释和维护?
你有两组抽象,它们总是在一起(你的 Repo
和 IApiClientConfiguration
)被另一个抽象分开 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);
}
那么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
}
我对使用依赖注入做一切感到迷惑。但在这种情况下,为了可读性,我们可以只创建一个 class 组成一个具有给定配置的 API 客户端。
FooApiClient
耦合到 FooClientConfiguration
(我们通常使用 DI 试图避免的那种事情)但这就是它的目的 - 提供耦合配置,以便它不存在于DI 配置或依赖于它的 classes。
现在杂乱的东西被移出 DI 配置,FooRepo
甚至更简单,因为它依赖于 true 抽象,而不是具有 I
在它前面但实际上代表了一个具体的实现(一个 API 客户端。)
假设我有两个由 Castle Windsor 实例化的 classes,并且每个都依赖于同一接口:
FooRepo
→IApiClient
BarRepo
→IApiClient
在这种情况下,IApiClient
由知道如何与任何 API 通信的 class、GenericApiClient
实现。但是,我想创建 GenericApiClient
的不同实例,这些实例传递不同的配置值(通过 IApiClientConfiguration
公开),以便 FooRepo
与 Foo API 端点和 BarRepo
与酒吧对话 API 端点:
FooRepo
→IApiClient (GenericApiClient)
→IApiClientConfiguration (FooClientConfiguration)
BarRepo
→IApiClient (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
获得配置了 FooClientConfiguration
的 GenericApiClient
实例,BarRepo
获得 [=32] =]:
- 默认情况下,它们都得到
FooClientConfiguration
,因为这是最先注册的 - 我可以使用
DependsOn(...)
覆盖IApiClient
的配置,但这适用于FooRepo
和BarRepo
- 如果我在
BarRepo
上使用DependsOn(...)
,它没有效果
(如果上面的问题不清楚,我有一个最小的工作示例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
的两个不同实现,并且您正在为每个指定要使用的配置。然后,在注册 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"))
);
}
@Scott-Hannen 给出的答案技术上是正确的,他说
也是正确的It's not pretty.
Windsor 非常灵活,您可以通过足够的配置和扩展让它做很多事情,但是您应该这样做吗?
通常,如果事情变得太复杂,并且您发现自己无法向工具解释您的架构是如何组合在一起的,也许它太复杂而无法向人类解释和维护?
你有两组抽象,它们总是在一起(你的 Repo
和 IApiClientConfiguration
)被另一个抽象分开 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);
}
那么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
}
我对使用依赖注入做一切感到迷惑。但在这种情况下,为了可读性,我们可以只创建一个 class 组成一个具有给定配置的 API 客户端。
FooApiClient
耦合到 FooClientConfiguration
(我们通常使用 DI 试图避免的那种事情)但这就是它的目的 - 提供耦合配置,以便它不存在于DI 配置或依赖于它的 classes。
现在杂乱的东西被移出 DI 配置,FooRepo
甚至更简单,因为它依赖于 true 抽象,而不是具有 I
在它前面但实际上代表了一个具体的实现(一个 API 客户端。)