创建销毁对象(第五条:推崇依赖注入而不是强硬地将资源组合起来)

许多类依赖一个或者多个基础的资源。比如,一个拼写检查器会依赖于一个字典。这种类用静态工具类(Item 4)来做是很常见的。

//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) {
    }
}

同样的,用单例(Item 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 suggestions(String typo) {...}

}

这些方法都不能令人满意,因为它假设只有一个字典是有用的。在现实中,每种语言都有自己的字典,而且还有一些特殊的字典用于特殊的词汇。而且,一个用于测试的特别的词典也可能是很需要的。存在一个能在所有情况下都满足的词典的这种假设就只能是痴心妄想。尽管可以通过把dictionary的属性设置为nonfinal的并且加上能改变检查器中词典的方法,让拼写检查器支持多个词典,然而这种做法是糟糕的,易错的,而且在并发的环境中是会挂掉的。一个类的行为要是依据基础的资源做成参数化了,那么静态工具类还有单例都是不适合的。而真正需要的有提供类的多个实例(在我们的例子中,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 suggestions(String typo) { ...}

}

依赖注入模式是如此的简单,所以很多开发人员用了它多年却还不知道它有个名字。我们的拼写检查器的例子仅有一个资源(词典),无论依赖多少资源或者依赖何种graphs,依赖注入都会工作的。因为它能够维护不变性(Item 17),所以多个客户端可以共享依赖的对象(假设客户端都想要相同的基础资源)。依赖注入适用于构造器,同样的,还有静态工厂(Item 1)和建造者(Item 2)。这种模式的一个有用的变体是把资源工厂传递到构造器。这种工厂象征着工厂方法模式。Java8里面介绍的Supplier接口,用来代表工厂堪称完美。为了让客户端能够传递创建声明类型的所有子类,使用Supplier作为参数的方法通常应该用有界的通配类型(Item 31)来限制工厂的类型参数。例如,这有一个方法来制造镶嵌图案,它会让客户端提供的工厂来制作每一个瓷砖。

Mosaiccreate(Supplier tileFactory) { ... }

尽管依赖注入会极大的提高灵活性还有可测试性,但是它仍会使得大的项目变得杂乱,因为这趟的工程通常包含成千上万个依赖。这种堆积可以通过依赖注入框架来消除,比如DaggerGuice,或者Spring。虽然这些框架的使用超出了这本书,但是这些手动实现依赖注入的API仍然被这些框架所改编的来去使用的。

总之,如果一个类依赖一些基本的资源,并且这些资源会影响类的行为,不要使用单例或者静态工具类去实现它们,也不要让类直接去创建这些资源。而是将这些资源或者创建资源的工厂传递给构造器(或者静态工厂或者建造者)。这种被称为依赖注入的做法会极大的增强类的灵活性,可重用性和可测试性。

你可能感兴趣的:(Effective,Java,3)