有没有替代混蛋注射? (通过默认的构造函数也是穷人的注入)
我通常很想在less数情况下使用“混蛋注射”。 当我有一个“适当的”dependency injection构造函数:
public class ThingMaker { ... public ThingMaker(IThingSource source){ _source = source; }
但是,对于我打算作为公共API (其他开发团队将使用的类)的类,我永远不会find一个更好的select,而不是写一个具有最可能需要的依赖性的默认“混蛋”构造函数:
public ThingMaker() : this(new DefaultThingSource()) {} ... }
这里明显的缺点是这会在DefaultThingSource上创build一个静态依赖关系; 理想情况下,不会有这样的依赖,消费者总是会注入他们想要的任何IThingSource。 但是,这太难使用了; 消费者希望创造一个ThingMaker并开始制作东西,然后在需要时再注入其他东西。 在我看来,这只剩下几个选项:
- 省略混蛋构造函数; 强迫ThingMaker的用户了解IThingSource,了解ThingMaker如何与IThingSource交互,查找或编写具体类,然后在构造函数调用中注入实例。
- 省略混蛋构造函数,并提供单独的工厂,容器或其他引导类/方法; 不知何故使消费者明白,他们不需要编写自己的IThingSource; 强迫ThingMaker的用户find并理解工厂或引导程序并使用它。
- 保持混蛋的构造函数,使消费者“新build”一个对象并运行,并应对DefaultThingSource上的可选静态依赖。
男孩,#3肯定看起来很有吸引力。 还有更好的select吗? #1或#2似乎不值得。
据我所知,这个问题涉及如何公开一个松散耦合的API与一些适当的默认值。 在这种情况下,您可能拥有一个良好的本地默认值 ,在这种情况下,可以将依赖关系视为可选项。 处理可选依赖项的一种方法是使用属性注入而不是构造器注入 – 事实上,这是属性注入的一种海报scheme。
但是,混淆注入的真正危险是默认情况下是一个Foreign Default ,因为这意味着默认的构造函数会拖拽到实现默认的程序集的不希望的耦合上。 然而,就我所了解的这个问题而言,预期的违约将源于同一个集会,在这种情况下,我没有看到任何特别的危险。
在任何情况下,你也可以考虑一个前面的答案中描述的Facade: dependency injection(DI)“友好”库
顺便说一句,这里使用的术语是基于我书中的模式语言。
我的权衡是@BrokenGlass上的一个转折:
1)唯一的构造函数是参数化的构造函数
2)使用工厂方法创build一个ThingMaker并通过该默认源。
public class ThingMaker { public ThingMaker(IThingSource source){ _source = source; } public static ThingMaker CreateDefault() { return new ThingMaker(new DefaultThingSource()); } }
显然这并不能消除你的依赖关系,但是它确实使我更清楚的知道,这个对象具有依赖关系,调用者可以深入地关注它。 如果你喜欢(CreateThingMakerWithDefaultThingSource),那么你可以使这个工厂方法更加明确,如果这有助于理解。 我更喜欢这个重写IThingSource工厂方法,因为它继续支持组合。 当DefaultThingSource被废弃时,你也可以添加一个新的工厂方法,并且有一个明确的方法来使用DefaultThingSourcefind所有的代码并将其标记为升级。
你在你的问题中涵盖了可能性。 在其他地方的工厂类为了方便或类内部的一些便利。 唯一的另一个没有吸引力的select就是基于反思,进一步隐藏依赖。
另一种方法是在ThingMaker
类中创build一个工厂方法 CreateThingSource()
,它为您创build依赖关系。
对于testing,或者如果您确实需要其他types的IThingSource
,则必须创buildThingMaker
的子类并重写CreateThingSource()
以返回所需的具体types。 显然这种方法是值得的,如果你主要需要能够注入依赖进行testing,但对于大多数/所有其他目的不需要另一个IThingSource
我投了3票。 您将会使您的生活变得更加简单 – 而其他开发人员的生活。
如果你必须有一个“默认”依赖,也就是穷人的dependency injection,那么你必须初始化和“连接”依赖。
我将保留这两个构造函数,但只有一个工厂用于初始化。
public class ThingMaker { private IThingSource _source; public ThingMaker(IThingSource source) { _source = source; } public ThingMaker() : this(ThingFactory.Current.CreateThingSource()) { } }
现在在工厂中创build默认实例并允许该方法被覆盖:
public class ThingFactory { public virtual IThingSource CreateThingSource() { return new DefaultThingSource(); } }
更新:
为什么使用两个构造函数:两个构造函数清楚地显示了这个类是如何使用的。 无参数构造函数的状态:只是创build一个实例,类将执行所有的职责。 现在第二个构造函数声明这个类依赖于IThingSource,并提供了一种使用与默认实现不同的实现的方法。
为什么要使用工厂: 1-纪律:创build新实例不应该是这个类的职责的一部分,工厂类更合适。 2- DRY:想象一下,在相同的API中,其他类也依赖于IThingSource并执行相同的操作。 一旦返回IThingSource的工厂方法和API中的所有类自动开始使用新的实例。
我没有看到将ThingMaker与IThingSource的默认实现相结合的问题,只要此实现对整个API有意义,并且您还提供了用于覆盖此依赖项以进行testing和扩展的方法。
你不喜欢这种依赖的OO杂质,但是你并没有真正地说出它最终造成的麻烦。
- ThingMaker是否以任何不符合IThingSource的方式使用DefaultThingSource? 没有。
- 难道有一段时间你会被迫退休无参数的构造函数吗? 既然你能够在这个时候提供默认的实现,不太可能。
我认为这里最大的问题是名称的select,而不是是否使用该技术。
通常与这种注入方式有关的例子通常是非常简单的:“在类B
的默认构造函数中,用new A()
调用一个重载的构造函数,并且在你的路上!
现实情况是依赖往往是非常复杂的构build。 例如,如果B
需要像数据库连接或应用程序设置那样的非类依赖项呢? 然后,将类B
绑定到System.Configuration
命名空间,增加其复杂性和耦合性,同时降低其一致性,所有这些都可以通过省略默认构造函数来简化外部化。
这种注入方式告诉读者,你已经认识到解耦devise的好处,但不愿意承诺。 我们都知道,当有人看到那个多汁,轻松,低摩擦的默认构造函数时,他们会称之为无论从那时起它的程序有多严格。 如果没有阅读默认构造函数的源代码,他们就无法理解他们程序的结构,当你只是分发程序集时,这不是一个选项。 您可以logging连接string名称和应用程序设置关键字的约定,但是在这一点上,代码并不是独立存在的,您将责任交给开发人员来追究正确的咒语。
优化代码,让写这些代码的人不用理解他们所说的是一个警笛歌曲,一个反模式,最终导致比在初始工作中节省的时间更多地浪费在解开魔法上。 要么去耦,要么不去; 保持每一个模式的脚都减less了两者的重点。
对于什么是值得的,我在Java中看到的所有标准代码都是这样的:
public class ThingMaker { private IThingSource iThingSource; public ThingMaker() { iThingSource = createIThingSource(); } public virtual IThingSource createIThingSource() { return new DefaultThingSource(); } }
任何不需要DefaultThingSource
对象的人都可以重写createIThingSource
。 (如果可能的话,调用createIThingSource
将会是构造函数以外的地方。)C#不鼓励像Java一样重写,它可能不像Java那样明显,用户可以并且可能应该提供自己的IThingSource实现。 ( 如何提供它也不是那么明显)。我的猜测是#3是要走的路,但我想我会提到这一点。
只是一个想法 – 也许有点更优雅,但可悲的是没有摆脱依赖:
- 删除“混蛋构造函数”
- 在标准构造函数中,您将源参数默认为null
- 那么你检查源是空的,如果是这种情况,你分配它“新的DefaultThingSource()”otherweise无论消费者注入
有一个内部工厂(库的内部),将DefaultThingSource映射到从默认构造函数调用的IThingSource。
这使您可以在没有参数或IThingSource的任何知识的情况下“更新”ThingMaker类,并且不需要对DefaultThingSource的直接依赖。
对于真正的公共API,我通常使用两部分的方法处理:
- 在API中创build一个帮助器,以允许API消费者使用他们所select的IoC容器从API注册“默认”接口实现。
- 如果希望允许API消费者在没有他们自己的IoC容器的情况下使用API,则在填充了相同“默认”实现的API内容纳可选容器。
这里真正棘手的部分是决定何时启动容器#2,最好的select方法将严重依赖于您的API消费者。
我支持选项#1,有一个扩展: 使DefaultThingSource
成为公共类 。 上面的措辞意味着DefaultThingSource
将隐藏在API的公共消费者身上,但是据我了解,您没有理由不公开默认值。 而且,你可以很容易地logging一下,在特殊情况之外,一个new DefaultThingSource()
总是可以传递给ThingMaker
。