有许多类会依赖一个或者多个底层的资源,例如:拼写检查器需要依赖词典。因此,像下面这种把类实现为静态工具类的做法很常见。
public class SpellChecker{
private static final Lexicon dictionary = ...;
private SpellChecker(){
}
public static boolean isValid(String word){
}
public static List<String> suggestions(String typo){
}
}
同样的,将这些类实现为Singleton的做法也并不少见
public class SpellChecker{
private static final Lexicon dictionary = ...;
private SpellChecker(){
}
public static final INSTANCE = new SpellChecker();
public static boolean isValid(String word){
}
public static List<String> suggestions(String typo){
}
}
以上两种方式都不理想,因为它们都假定为只有一本词典,实际上,每种语言都有自己不同的词典,特殊语言还有特殊的词典等等。因此假定用一本词典来满足所有需求,是不现实的。所以静态工厂和Singleton不适合用于需要引用底层资源的类。
这里需要的是能支持类的多个实例,每一个实例都使用客户端指定资源。满足需求最简单的模式是:当创建一个新的实例时,就将资源传入到构造器,这就是依赖注入
的一种形式:词典是拼音检查器的一个依赖,在创建拼音检查器时就将词典注入到其中。
public class SpellChecker{
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary){
this.dictionary = dictionary;
}
public boolean isValid(String word){
}
public List<String> suggestions(String typo){
}
}
这种模式的另一个变体是,将资源工厂传给构造器,工厂是可以被重复调用来创建类的实例的一个对象,在Java8中增加的接口Supplier<T>,最适合用于表示工厂,带有Supplier<T>的方法,通常应该限制输入工厂的类型参数,使用有限制的通配符类型,以便来创建指定限定类型的任意子类型:例如
public class DependencyInjection {
public static void main(String[] args) {
SpellChecker spellChecker = new SpellChecker(ChineseLexicon::new);
}
}
class SpellChecker {
private final Lexicon dictionary;
// public SpellChecker(Lexicon dictionary){
// this.dictionary = dictionary;
// }
public SpellChecker(Supplier<? extends Lexicon> dictionary) {
this.dictionary = dictionary.get();
}
}
interface Lexicon {
}
class ChineseLexicon implements Lexicon {
}
public class DependencyInjection {
public static void main(String[] args) {
SpellChecker spellChecker = new SpellChecker(ChineseLexicon::new);
}
}
虽然依赖注入极大的提升了灵活性和可测试性,但是如果使用过度,会导致凌乱不堪,因为可能包含上千个依赖,不过这种凌乱,用一个依赖注入框架就可以终结,如Spring,Dagger,Guice等。但是设计成手动依赖注入的API,一般都适用于这些框架。
总而言之,不要用Singleton和静态工具类来实现依赖注入一个或者多个底层资源,且该资源的行为会影响到该类的行为,也不要直接用这个类来创建资源,而是将这些资源或者工厂传递给构造器,通过它来创建资源类,这种实践被称为依赖注入,它极大的提高了类的灵活性、可重用性何可测试性。
网友评论