05. 依赖注入优于硬连接资源(hardwiring resources)

      同样地,将它们实现为单例的做法也并不少见(详见第 3 条):

      这两种方法都不令人满意,因为他们都是假设只有一本字典可用。实际上,每种语言都有自己的字典,特殊的字典被用于特殊的词汇表。另外,可能还需要用特殊的词典进行测试。想当然地认为一本字典就足够了,这是一厢情愿的想法。

      这里所需要的是能够支持类的多个实例 (在我们的示例中是指 SpellChecker),每个实例都使用客户端所期望的资源(在我们的例子中是 )。满足这一需求的简单模式是,在创建新实例时将资源传递到构造器中。 这是依赖项注入(dependency injection)的一种形式:字典是拼写检查器的一个依赖项,当它创建时被注入到拼写检查器中。

      依赖注入模式非常简单,许多程序员使用它多年而不知道它有一个名字。 虽然我们的拼写检查器的例子只有一个资源(字典),但是依赖项注入可以使用任意数量的资源和任意依赖图。 它保持了不变性(详见第 17 条),因此多个客户端可以共享依赖对象(假设客户需要相同的底层资源)。 依赖注入同样适用于构造器、静态工厂(详见第 1 条)和 builder 模式(详见第 2 条)。

      尽管依赖注入极大地提高了灵活性和可测试性,但它可能使大型项目变得混乱,这些项目通常包含数千个依赖项。使用依赖注入框架(如 Dagger [Dagger]、Guice [Guice] 或 Spring [Spring])可以消除这些混乱。这些框架的使用超出了本书的范围,但是请注意,为手动依赖注入而设计的 API 非常适合这些框架的使用。

      总而言之,不要用单例和静态工具类来实现依赖一个或多个底层资源的类,且该资源的行为会影响到该类的行为;也不要直接用这个类来创建这些资源。而应该将这些资源或者工厂传给构造器(或者静态工厂,或者构建器),通过它们来创建类。这个实践就被称作依赖注人,它极大地提升了类的灵活性、可重用性和可测试性。