我现在选择了第一个选项,但我觉得不太舒服,我相信一定有更好的方法。
很多时候,第一个选项很好——所以你应该练习适应它。这主要意味着更多地考虑依赖注入的用途,并清楚地了解这些力量是否在这里发挥作用。
如果 Banner 是一个实体,在domain-driven-design 意义上,那么它可能类似于内存状态机。它有一个它管理的数据结构,以及一些用于更改该数据结构或回答有关该数据结构的有趣问题的功能,但它没有 I/O、数据库、网络等问题。
这反过来表明您可以在所有上下文中以相同的方式运行它 - 您不需要一堆替代实现来使其可测试。您只需实例化一个并调用它的方法。
如果它在所有上下文中都以相同的方式运行,那么它不需要可配置的行为。如果你不需要能够配置行为,那么你就不需要依赖注入(因为这个实体的所有副本都将使用(副本)相同的依赖项。
当您确实有可配置的行为时,分析将需要查看范围。如果您需要能够将该行为从一次调用更改为下一次调用,那么调用者将需要了解它。如果行为变化的频率低于此值,那么您可以开始研究“构造函数注入”是否有意义。
您知道您打算对给定的方法调用使用单个 BannerReplacer,因此您可以立即从如下所示的方法开始:
class Banner {
void doTheThing(arg, bannerReplacer) {
/* do the bannerReplacer thing */
}
}
请注意,此签名完全不依赖bannerReplacer 的生命周期。更具体地说,BannerReplacer 的生命周期可能比 Banner 更长,或者更短。我们只关心生命周期是否比 doTheThing 方法长。
class Banner {
void doTheThing(arg) {
this.doTheThing(arg, new BannerReplacer())
}
// ...
}
这里,调用者根本不需要知道 BannerReplacer;我们每次都会使用默认实现的新副本。调用者关心使用哪个实现可以自己传递。
class Banner {
bannerReplacer = new BannerReplacer()
void doTheThing(arg) {
this.doTheThing(arg, this.bannerReplacer)
}
// ...
}
和以前一样;我们只是使用了一个生命周期更长的 BannerReplacer 实例。
class Banner {
Banner() {
this(new BannerReplacer())
}
Banner(bannerReplacer) {
this.bannerReplacer = bannerReplacer;
}
void doTheThing(arg) {
this.doTheThing(arg, this.bannerReplacer)
}
// ...
}
与以前的想法相同,但现在我们允许“注入”默认实现,该实现可以比给定的 Banner 实例寿命更长。
从长远来看,舒适来自于进行分析以了解当前问题的需求,以便您可以选择合适的工具。