许多类依赖一个或者多个底层资源,如:拼写检查类依赖于一个字典类
// 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 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 suggestions(String typo) { ... }
}
这两种写法都不是令人满意的,因为这两种写法不支持字典类的替换,都假定只提供一种字典类。
缺点如下:
不够灵活、不可测试
因此
静态工具类与单例类都不适用于依赖底层资源的情景
更好的做法是
在创建类实例的时候将依赖资源通过构造函数传递进去
这是依赖注入的一种形式:将拼写检查类依赖的字典类通过构造函数注入进来
// 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 suggestions(String typo) { ... }
}
同时,依赖注入还支持多个类依赖同一个底层资源
依赖注入通过构造器、静态工厂、Builder注入都是等效的
该模式的一种非常实用的变体:向构造函数传递一个资源的工厂类
Java8可用Supplier<T>接口来表示此工厂来提供资源的实例
对于使用Supplier<T>作为输入参数的方法,应该提供一个类型限定的参数作为输入,允许客户端传入一个创建子类的工厂。
Mosaic create(Supplier tileFactory) { ... }
尽管依赖注入极大的提高了灵活性和可测试性,它同时也使大的项目更加杂乱,但是这可以使用依赖注入框架来解决
总之,不要使用单例和工具类来实现一个类依赖另一个底层资源
而应该,传递资源或者创建资源的工厂到构造器(或者工厂方法、builder)
这种实践将极大的增加类的灵活性、重用性和可测试性