【问题标题】:Dependency injection exposes business layer internals to UI layer - surely a bad thing?依赖注入将业务层内部暴露给 UI 层——这肯定是件坏事吗?
【发布时间】:2011-09-14 11:25:58
【问题描述】:

我目前正在使用依赖注入。实际上,这涉及到 UI 层(例如 Web 应用程序),其中包括一个 DI 容器,该容器具有关于它将使用的接口的一大堆数据,以及每个接口要使用的实现。

但是,由于需要满足依赖关系,DI 容器还需要了解它原本不需要访问的接口和类。例如:

UI 层需要使用IWidgetManager 接口。配置的实现是ConcreteWidgetManager。这是在 DI 容器中注册的。

ConcreteWidgetManager 依赖于 IWidgetRepository。所需的实现是ConcreteWidgetRepository。因此DI容器也必须知道IWidgetRepositoryConcreteWidgetRepository

如果我只是硬编码ConcreteWidgetManagerConcreteWidgetRepository 的依赖,那么ConcreteWidgetRepository 可以在内部进行,因此对我的UI 层不可见。没有任何 UI 代码可以绕过管理器层并直接与存储库层一起工作。

因此,从 UI 层的角度来看,尽管 DI 的使用在许多方面都是一种很棒的模式,但似乎破坏了封装。

我的想法是否正确,有什么办法可以缓解这种情况吗?

【问题讨论】:

  • 答案是肯定的,DI 确实打破了封装。它是 stackoverflow.com/questions/1005473/… 的副本,另请参阅 bryancook.net/2011/08/…
  • ConcreteWidgetRepository 如何对 UI 层可见?框架配置中提到它的事实并不意味着它是“可见的”,除非您的 UI 代码具有对 IWidgetRepository 的自动关联引用。这是一个编码问题,它完全等同于您的 UI 代码,其中包含对ConcreteWidgetRepository的硬编码引用
  • @parsifal - DI 容器是 UI 层的一部分。如果它需要知道ConcreteWidgetRepository(它确实需要将一个注入ConcreteWidgetManager),那么该知识也可以在UI 层的其他地方免费获得。
  • @David - 在我看来,DI 容器 应用程序:它将 UI 与业务逻辑相关联,并连接任何内部依赖项,但它不是 部分
  • @parsifal - 我不知道你在什么平台上开发,所以我不太确定如何提出问题,所以我将把它交给你 .NET 风格。那么你如何创建你的解决方案呢? DI 容器与 UI 项目(例如 Web 应用程序)在不同的项目中?

标签: design-patterns dependency-injection encapsulation


【解决方案1】:

不,依赖注入不会破坏封装。破坏封装的是您对应用程序进行分层的方式。通过在不同的程序集中分离接口和实现并将容器配置放在自己的程序集中,您可以防止 UI 层必须依赖具体的ConcreteWidgetManager,如果您愿意,甚至可以依赖IWidgetRepository

但是,添加额外的组件是有成本的。在维护和编译解决方案所需的时间上。我不会使用程序集来强制执行架构规则,而是使用清晰简单的指南来强制执行它们,或者可能使用 NDepend 之类的工具来强制执行它们。然而,当团队足够小时,使用代码审查将足以有效地防止架构侵蚀。

【讨论】:

  • 这里有很多有趣的事情要考虑。谢谢。
  • 我想花一些时间建立一个快速的解决方案,看看我是否可以让这种方法发挥作用。如果它确实有效,我会给你积分!
  • 好的,我已经尝试了您关于将接口和实现放在不同程序集中的建议,并且它有效! UI 程序集需要对接口程序集的引用,以及对执行 DI 解析的“接线”程序集的引用。接线组件对接口组件和实现组件都有引用。因此,UI 程序集没有直接引用实现程序集,但仍然获得注入的实现!这绝对是我问题的答案。
【解决方案2】:

您所描述的方式意味着 UI 层负责创建 DI 容器。

这在您的特定应用程序中可能是正确的。但请记住,初始化部分只是整个代码的一小部分。

是的,有一些代码可以按特定顺序创建 DI 容器和 UI 层。 UI 层可能会调用函数CreateDIContainer,该函数初始化所有组件。但是这个函数是唯一提到实现的实例。 UI 层的所有其他方面都处理抽象接口。纯粹主义者可能会提出问题,但实际上,在 ISN'T CreateDIContainer 的 99.5% 代码中,UI 不知道实现是什么。将初始化函数移动到单独库中的单独静态类中,如果这会让您更快乐。


我也好奇你说

如果我只是硬编码了 ConcreteWidgetManager 对 ConcreteWidgetRepository 的依赖 ....,

你提出它作为一个选项让我想知道:你有什么理由不硬编码这种关系吗?我在必要时使用依赖注入——最终的好处是我可以在我的自动化测试中模拟掉部分代码。

您要模拟出ConcreteWidgetRepository 吗?如果您不是,请继续对关系进行硬编码。否则,您只是为了架构而引入架构。

(见You Aren't Gonna Need It。)

【讨论】:

  • DI 容器确实只包含一小部分 UI 层代码,但它需要了解这些类的事实意味着 UI 层的其余部分也是如此。
  • 关于是否对依赖项进行硬编码的观点很有趣。问题是我想要另一个实现的可能性有多大?有时,我需要另一个实现(例如更改提供者)的“操作”原因,但更多时候是出于单元测试的目的。我的直觉是对任何服务类使用接口和 DI,除非我很确定我不需要其他实现。
  • “服务”类是指任何暴露行为而不是简单状态的东西。
  • @David - 关于是否对依赖项进行硬编码 - 这是我遵循 YAGNI 原则的地方。 (c2.com/cgi/wiki?YouArentGonnaNeedIt) 当你知道这会解决问题时,没有理由不让它成为注入依赖项。
  • 酷,感谢您的想法。这个问题真的让我又想起了DI。
【解决方案3】:

是的,它确实破坏了封装。但这就是使用 DI 需要付出的代价。它使您的代码更具可测试性,但是会破坏封装。

从类的 API 角度来看,要求注入依赖项的类变得尽可能愚蠢,但调用该类的类现在知道的太多了。在你的情况下,框架。如果您在代码中一般使用 DI,例如构造函数注入,则调用类需要了解几个类。

【讨论】:

    猜你喜欢
    • 2012-08-01
    • 1970-01-01
    • 2021-11-06
    • 1970-01-01
    • 2015-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-26
    相关资源
    最近更新 更多