依赖注入与服务定位器-架构快速进阶教程
1. 概述
在本教程中,我们将回顾两种模式:依赖项注入和服务定位器。他们以不同的方式解决相同的问题,并且经常使用可以应用于两者的术语。目标是找出它们的本质区别以及每种方法的优缺点。
2. 示例
假设我们有一组带有依赖项的类。让我们概述一下初始布局:
该图仅显示类之间的依赖关系。但是,它没有解释这些类是如何填充依赖项的。让我们使用一种朴素的方法,直接在每个类中实例化所需的依赖项:
允许类定义其依赖项将破坏单一责任原则,并使其僵化和脆弱。另一个问题是new运算符,它显式地为具体实现创建依赖项,如上图所示。因此,我们有两个问题:类负责定义它们的依赖关系,依赖关系依赖于具体的实现。
3. 服务定位器
服务定位器的主要思想是创建一个包含所有依赖项的注册表,并在需要时从中获取组件。每个需要此注册表中的某些内容的对象都将与之交互,而不是尝试实例化依赖项本身。这样,我们可以从组件中删除对具体实现的所有依赖:

但是,该对象仍将具有依赖项,但仅适用于服务定位器本身,它将透明地为我们提供所需的实现。不幸的是,太透明了,因为它会对客户端隐藏所有依赖项。从技术上讲,它打破了封装,因为与对象的任何交互都需要查看组件内部,尤其是在测试时。
另一个问题是服务定位器将编译时错误推迟到运行时。直接在方法中使用服务定位器会产生一个更重要的问题。由于服务定位器注册表是在运行时填充的,因此当我们请求所需的类时,很难判断它们是否在那里。依赖关系注入容器可能会为应用程序生命周期后期创建的动态部件引入相同的问题。
此外,服务定位器被指控违反接口隔离原则。然而,这是有争议的,因为“注册表”本身可能被认为是元编程的一部分,通常的设计原则应该谨慎应用。与服务定位器不同,抽象工厂和工厂更明确地声明其依赖项。
此外,人们指出了可测试性的问题,但如果服务定位器的实现足够灵活,这不是一个真正的问题。在大多数情况下,如果服务定位器作为单一实例实现或不允许使用简单的方法来配置注册表,则会出现此问题。
服务定位器需要一个专用位置来填充注册表中的组件。这样,我们可以将创建逻辑与将使用它们的类分开。这种分离是必不可少的,特别是如果我们有一个复杂的依赖关系图。
4. 依赖注入
这是同一问题的另一种方法,非常直观和直接。如果我们不想在类中创建依赖项,让我们请其他人向我们提供它们。此方法创建合同。该类要使其正常运行,需要传递给它的一定数量的依赖项:
依赖注入解决了我们之前讨论的两个问题。但是,我们仍然有一个专用的类来配置和创建所有需要的依赖项。通常,依赖关系注入与依赖关系注入容器结合使用,如果使用不当,可能会导致服务定位器部分中讨论的所有问题。
有几种方法可以实现它:构造函数和 setter 注入(还有一个接口注入,但它没有广泛使用,而且更麻烦。总体而言,使用构造函数注入的方法更可取。同时,二传手注射有其好处,也可以使用。最主要的是要记住,二传手注入可能允许非完全构造的对象和循环依赖;因此,应该明智地使用它。
5. 依赖注入与服务定位器
这些方法之间的主要区别之一是它们对引入的依赖项的透明度。依赖关系注入对类中使用的所有内容都是显式的,并且通过构造函数注入,它清楚地显示在类 API 中。虽然服务定位器将所有内容隐藏在内部,并且很难分辨哪些类依赖于哪个类,但唯一清楚的是,它们都依赖于服务定位器。可以使用隔离接口实现服务定位器,但这可能会导致接炸。尽管现代 IDE 可以显示依赖关系图并构建清晰的 UML 关系图,但 API 并不知道依赖关系。
另一点是依赖注入的侵入性较小,并允许可重用性。这些类直接依赖于其他类,允许它们正常运行。使用服务定位器,重用类的唯一方法是将它们与服务定位器本身重用;因此,它们成包。尽管这两种模式都依赖于构造和配置应用程序对象的类,但依赖关系注入允许更好的可重用性和更直接的测试。
总体而言,依赖注入是实现松散耦合并允许重用组件的常用方法。它非常直观,允许我们更明确地声明依赖项。服务定位器可以提供更大的灵活性,但通常太多。这种广泛的灵活性可以打破组件之间的所有界限,并鼓励不良做法,因为所有组件在我们需要时始终可用。服务定位器通常可以被视为一种反模式,因为它可能会引入更多的问题和代码气味而不是好处。
6. 结论
服务定位器和依赖关系注入是尝试解决相同问题的模式。但是,它们具有完全不同的方法,并且每种方法都有其优点和缺点。尽管在某些情况下,服务定位器通常更适用,但依赖关系注入提供了更大的灵活性和可扩展性。依赖关系注入是在应用程序中实现松散耦合的最常见方法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端