马克·西曼关于私生子注射的相互矛盾的陈述。需要一些澄清

2024-01-04

我正在读他的书网络中的依赖注入 https://rads.stackoverflow.com/amzn/click/com/1935182501.

1) Here https://stackoverflow.com/questions/8393357/is-this-a-good-example-of-bastard-injection-anti-pattern他是这么说的Bastard Injection仅当我们使用时才会发生Foreign Default.

但在他的书中,第 148 页的插图表明Bastard Injection当依赖项的默认实现是以下任一情况时发生Foreign Default or Local Default:

当依赖项的默认实现是 a 时,Bastard Injection 反模式也会发生吗?Local Default?

2) Here https://stackoverflow.com/questions/6733667/is-there-an-alternative-to-bastard-injection-aka-poor-mans-injection-via-defa/6739953#6739953(也在他的书中)他指出,一个班级有一个可选依赖项,假设一个默认实现这种依赖性是一个很好的Local Default:

但在接下来的article http://www.infoq.com/articles/Succeeding-Dependency-Injection他似乎反对可选的依赖项无论如何,即使默认实现 is a Local Default:

private readonly ILog log;
public MyConsumer(ILog log)
{
    this.log = log ??LogManager.GetLogger("My");
}

在封装方面,这种方法的主要问题是 看来 MyConsumer 类还不能真正下定决心 它是否控制其日志依赖项的创建。尽管 这是一个简化的示例,如果 ILog 可能会出现问题 LogManager 返回的实例包装了一个非托管资源,该资源 不再需要时应将其丢弃。

当依赖项的默认实现是本地时,他在上面摘录中的论点是否也有效?如果是这样,那么还应该避免与本地默认值的可选依赖关系吗?

3) 第 3 页。 147:

Bastard Injection 的主要问题是它使用了 FOREIGN DEFAULT ...,我们不能再自由地重用该类,因为它拖累了 沿着我们可能不想要的依赖关系。也变得更加困难 进行并行开发,因为该类强烈依赖于它的 依赖性。

外部默认值是用作默认值的依赖项的实现,并且在与其使用者不同的程序集中定义。因此,使用外部默认值时,消费者的程序集也会拖拽依赖项的程序集。

他是否还暗示外国默认会使并行开发变得更加困难,而本地默认则不会?如果他是,那么那就没有意义,因为我认为并行开发变得困难的原因并不是消费者的程序集对依赖项的程序集有硬引用,而是消费者类依赖于一个具体实现的事实依赖性?

thanks


由于这里有很多问题,我将首先尝试综合阐述我对这个主题的看法,然后根据这些材料明确回答每个问题。

合成

当我写的时候the book http://affiliate.manning.com/idevaffiliate.php?id=1150_236,我首先尝试描述我在野外目睹的模式和反模式。因此,本书中的模式和反模式首先是描述性的,并且只是在较小程度上规定性的。显然,将它们分为patterns and 反模式暗示一定程度的判断:)

Bastard Injection 存在多个层面的问题:

  • 包依赖
  • 封装
  • 使用方便

最危险的问题与包依赖关系。这是我试图通过引入术语来使其更具可操作性的概念国外违约 versus 本地默认值。问题在于国外违约是它们拖累了硬耦合的依赖关系,这使得(去/重新)合成变得不可能 https://stackoverflow.com/a/9503612/126014。更明确地处理包管理的一个好资源是敏捷原则、模式和实践 http://amzn.to/19W4JHk.

就水平而言封装,这样的代码很难推理:

private readonly ILog log;
public MyConsumer(ILog log)
{
    this.log = log ??LogManager.GetLogger("My");
}

虽然它保护了类的不变量,但问题是在这种情况下, null是可接受的输入值。这并非总是如此。在上面的例子中,LogManager.GetLogger("My")可能只介绍一个本地默认值。从这段代码片段中,我们无法知道这是否属实,但为了便于论证,我们现在假设这一点。如果默认ILog确实是一个本地默认值,客户MyConsumer可以通过null代替ILog。请记住,封装的目的是使客户端能够轻松地使用对象,而无需了解所有实现细节。这意味着这是客户看到的全部内容:

public MyConsumer(ILog log)

在 C#(和类似语言)中,可以通过null代替ILog,它将编译:

var mc = new MyConsumer(null);

通过上述实现,不仅可以编译,而且还可以在运行时运行。根据波斯特尔定律 http://en.wikipedia.org/wiki/Robustness_principle,这是一件好事,对吧?

不幸的是,事实并非如此。

考虑另一个类required依赖性;我们称其为存储库,只是因为这是一个众所周知的(尽管被过度使用)模式:

private readonly IRepository repository;
public MyOtherConsumer(IRepository repository)
{
    if (repository == null)
        throw new ArgumentNullException("repository");

    this.repository = repository;
}

为了与封装保持一致,客户端只能看到以下内容:

public MyOtherConsumer(IRepository repository)

根据以前的经验,程序员可能倾向于编写这样的代码:

var moc = new MyOtherConsumer(null);

这仍然可以编译,但是运行时失败!

如何区分这两个构造函数?

public MyConsumer(ILog log)
public MyOtherConsumer(IRepository repository)

你不能,但目前,你的行为不一致:在一种情况下,null是一个有效的论点,但在另一种情况下,null将导致运行时异常。这将降低每个客户端程序员对 API 的信任。存在持续的是一个更好的前进方向。

为了让班级像MyConsumer 更容易使用,你必须保持一致。这就是接受的理由null这是一个坏主意。更好的方法是使用构造函数链:

private readonly ILog log;

public MyConsumer() : this(LogManager.GetLogger("My")) {}

public MyConsumer(ILog log)
{
    if (log == null)
        throw new ArgumentNullException("log");

    this.log = log;
}

客户现在看到的是这样的:

public MyConsumer()
public MyConsumer(ILog log)

这与MyOtherConsumer因为如果你试图通过null代替ILog,您将收到运行时错误。

虽然这是技术上仍然是混蛋注射,我可以接受这个设计 https://stackoverflow.com/a/6739953/126014 for 本地默认值;事实上,我有时会设计这样的 API,因为它是许多语言中众所周知的习惯用法。

对于许多目的来说,这已经足够好了,但仍然违反了一个重要的设计原则:

显式优于隐式 http://www.python.org/dev/peps/pep-0020/

虽然构造函数链允许客户端使用MyConsumer有默认的ILog,没有简单的方法可以找出默认实例ILog将会。有时,这也很重要。

此外,默认构造函数的存在会带来风险,即一段代码将在外部调用该默认构造函数。成分根 http://blog.ploeh.dk/2011/07/28/CompositionRoot。如果发生这种情况,则说明您过早地将对象相互耦合,并且一旦完成此操作,您就无法将它们与组合根内部解耦。

因此,使用普通构造函数注入的风险较小:

private readonly ILog log;

public MyConsumer(ILog log)
{
    if (log == null)
        throw new ArgumentNullException("log");

    this.log = log;
}

你仍然可以作曲MyConsumer使用默认记录器:

var mc = new MyConsumer(LogManager.GetLogger("My"));

如果你想使本地默认值更容易发现,您可以将其作为工厂公开在某个地方,例如于MyConsumer类本身:

public static ILog CreateDefaultLog()
{
    return LogManager.GetLogger("My");
}

所有这些都为回答该问题中的具体子问题奠定了基础。

1. 当依赖项的默认实现是本地默认值时,Bastard Injection 反模式是否也会发生?

是的,从技术上讲,确实如此,但后果没那么严重。混蛋注射首先是一个描述,使您能够在遇到它时轻松识别它。

请注意,书中的上述插图描述了如何重构away来自私生子注射;不是如何识别的。

2. [应该] 与本地默认值的可选依赖关系 [...] 也应该避免吗?

从包依赖关系的角度来看,您不需要避免这些;它们相对良性。

从使用的角度来看,我仍然倾向于避免它们,但这取决于我正在构建的内容。

  • 如果我创建一个可重用库(例如OSS项目)很多人都会使用,我可能仍然会选择Constructor Chaining,以便更容易上手API。
  • 如果我创建一个仅在特定代码库中使用的类,我倾向于完全避免可选依赖项,而是在组合根中明确地组合所有内容。

3. 他是否也在暗示,Foreign Default 会使并行开发变得更加困难,而 Local Default 则不会?

不,我不。如果有默认的话,必须先默认到位才可以使用;是否是并不重要Local or Foreign.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

马克·西曼关于私生子注射的相互矛盾的陈述。需要一些澄清 的相关文章

随机推荐