许多类依赖于一个或多个底层资源。 例如,拼写检查器依赖于字典。常见的作法是将这些类实现为静态实用程序类(第4项):java
// 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) { ... } }
一样的,将它们做为单例实现的状况并很多见(第3项):并发
// 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) { ... } }
这些方法都不使人满意,由于它们假设只有一本值得使用的字典。 在实践中,每种语言都有本身的字典,特殊字典用于特殊词汇。 并且,可能须要使用特殊字典进行测试。 假设单本字典就足以知足全部状况,这是一厢情愿的想法。app
你能够尝试让SpellChecker支持多个词典,方法是使字典字段为非final域,并添加一个方法来更改现有拼写检查器中的字典,但这在并发时设置会很笨拙,容易出错而且不可行。 静态实用程序类和单例不适用于底层资源做为参数的类(Static utility classes and singletons are inappropriate for classes whose behavior is parameterized by an underlying resource.)。框架
所须要的是可以支持类的多个实例(在咱们的示例中为SpellChecker),每一个实例都使用客户端所需的资源(在咱们的示例中为字典)。 知足此要求的简单模式是在建立新实例时将资源传递给构造函数。 这是依赖注入的一种形式:字典是拼写检查器的依赖项,并在建立时注入拼写检查器。ide
// 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) { ... } }
这种依赖注入很简单,以致于程序猿用了不少年殊不知道它有一个名称。虽然咱们的拼写检查器只有一个资源(字典),可是依赖注入可使用任意数量的资源和任意的依赖关系,它保留了不变性(第17项),所以多个客户端能够共享依赖对象(假设客户端须要相同的底层资源)。依赖注入一样适用于构造函数、静态工厂(第1项)和构建器(第2项)。函数
将资源工厂传递给构造函数就会变成一个有用的模式。工厂是一个对象,经过重复调用这个工厂能够建立某个类型的实例对象。这些就是工厂方法模式 [Gamma95]。Java 8中引入的Supplier <T>接口很是适合体现工厂。在输入上采用Supplier <T>的方法一般应该使用泛型(第31项)约束工厂的类型参数,以容许客户端传入建立指定类型的任何子类型的工厂。例如,这是一种使用客户提供的工厂生成马赛克来生成每一个图块的方法:测试
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
尽管依赖注入极大地提升了灵活性和可测试性,但它可能会使大型项目更加混乱,这些项目一般包含数千个依赖项。经过使用依赖注入框架,例如Dagger [Dagger],Guice [Guice]或Spring [Spring],能够消除这种混乱。这些框架的使用超出了本书的范围,但请注意,为手动依赖注入而设计的API能够轻松地适用于这些框架。flex
总之,若是有一个类依赖一个或多个底层资源的类,而且底层资源类影响了类的行为,不要使用单例或静态实用程序类来实现它,而且不要让类直接建立这些资源(do not use a singleton or static utility class to implement a class that depends on one or more underlying resources whose behavior affects that of the class)。相反,将资源或工厂传递给构造函数(或静态工厂或构建器)。这种作法称为依赖注入,将极大地加强类的灵活性,可重用性和可测试性。ui