ITEM 5: PREFER DEPENDENCY INJECTION TO HARDWIRING RESOURCES
许多类依赖于一个或多个底层资源。例如,拼写检查器依赖于字典。我们通常能看到这样的静态工具类:
// Inappropriate use of static utility - inflexible & untestable!
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} // Noninstantiable
public static boolean isValid(String word) { ... }
public static List<String> suggestions(String typo) { ... }
}
它们也常被实现为单例:
// Inappropriate use of singleton - inflexible & untestable!
public class SpellChecker {
private final Lexicon dictionary = ...;
private SpellChecker(...) {}
public static INSTANCE = new SpellChecker(...);
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
这两种方法都不能令人满意,因为它们都假定只使用一本字典。在现实中,每种语言都有自己的字典,专业的词汇还会使用专门的字典。此外,最好再准备一个专门用于测试的字典。认为一本字典就足够了,这是一厢情愿的想法。
我们可以通过将 dictionary 字段设置为非final,并在现有 SpellChecker 中添加一个方法来更改字典,从而使 SpellChecker 支持多个字典,但是这样做会很麻烦,容易出错,并且在并发场景下不可用。静态工具类和单例不适用于其行为由底层资源参数化的类。
我们需要的是支持类的多个实例的能力(在我们的示例中是 SpellChecker ),每个实例都使用客户所需的资源(在我们的示例中是字典)。一种简单的方法是在创建新实例时将资源传递给构造函数。这是依赖注入的一种形式: 字典是 SpellChecker 的依赖项,在创建 SpellChecker 的实例时将其注入。
// Dependency injection provides flexibility and testability
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
依赖项注入模式非常简单,以至于许多程序员使用它很多年却不知道这项技术拥有一个名称。虽然我们的示例只有一个资源(字典),但是依赖项注入可以处理任意数量的资源和任意依赖关系图。它是保持不变的,因此多个用户可以共享依赖对象(假设用户希望使用相同的底层资源)。依赖项注入适用于构造函数、静态工厂和 Builder。这个模式的一个有用的变体是将资源工厂传递给构造函数。工厂是一个可以反复调用它来创建类型实例的对象(工厂方法模式)。Java 8中引入的 Supplier<T> 接口非常适合表示工厂。在输入上采用Supplier<T>的方法通常应该使用有界的通配符类型( bounded wildcard type) 约束工厂的类型参数,以允许客户端传入工厂,创建指定类型的任何子类型。 例如,下面是一个使用客户端提供的工厂生成类Tile实例的方法:
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
尽管依赖项注入极大地提高了灵活性和可测试性,但它会使大型项目变得混乱,而大型项目通常包含数千个依赖项。通过使用依赖注入框架(如Dagger 、Guice 、Spring ),可以几乎消除这种混乱。这些框架的使用超出了本书的范围,但是请注意,为手工依赖注入设计的api也很适合这些框架使用。
总之,不要使用单例或静态工具类来实现依赖一个或多个底层资源的类,这些底层资源的行为会影响类的行为,也不要让类直接创建这些资源。相反,将创建它们的资源或工厂传递给构造函数/静态工厂/Builder,将极大地增强类的灵活性、可重用性和可测试性,这种技术称为依赖注入。
网友评论