读《Effective Java》笔记 - 条目5

发布于:2024-11-27 ⋅ 阅读:(93) ⋅ 点赞:(0)

条目5:优先考虑通过依赖注入来连接资源 JAVA

静态工具类和Singleton的缺点

很多类,会依赖底层的一个或者多个资源。这时候可能会采用

  • 静态工具类
  • Singleton

但是这两种方案都不适合,静态工具类和Singleton都不够灵活且难以测试。比如:现在需要一个拼音检查器类。

采用静态工具类实现

public class SpellChecker{
    private static final Lexicon dictionary = ...;
    
    private SpellChecker () {} // 不可实例化
    
    public static boolean isValid (String word) {...}
    public static List<String> suggesstions (String type) {...}
}

采用Singleton实现

public class SpellChecker{
    private static final Lexicon dictionary = ...;
    
    private SpellChecker (...) {}
    
    public static SpellChecker INSTANCE = new SpellChecker(...);
    
    public static boolean isValid (String word) {...}
    public static List<String> suggesstions (String type) {...}
}

上面的方式,都是假定只有一本字典,如果是如果是多个呢?可以尝试将SpellChecker修改,去掉final,然后加入一个可以修改字典的方法。这种方式太笨拙了,而且还容易出错。

对于行为会被底层资源以参数化方式影响的类而言,静态工具类和Singleton类都不适合。

依赖注入

为了支持SpellChecker这个类的多个实例,每个实例都用不同的资源,满足该要求的简单方式就是,在创建新实例时,将资源传入构造器。这就是依赖注入。

public class SpellChecker{
    private final Lexicon dictionary;
    
    private SpellChecker (Lexicon dictionary) {
        this.dictionary = dictionary;
    }
    
    public static boolean isValid (String word) {...}
    public static List<String> suggesstions (String type) {...}
}

资源工厂

我们可以将资源工厂传入构造器,资源工厂是一个对象,可以被重复调用,来创建某个类型的实例。这里体现了工厂方法模式。Java8引入了Supplier<T>非常适合用来表示工厂,以Supplier<T>作为输入方法,通常应该使用有限的通配符类型,来约束工厂的类型参数,从而客户端传入一个它能够创建指定类型的任何子类型的对象的工厂。

我觉得上面书上说的有点绕。我简单表述一下,Mosaic 类有一个构造器,它接受一个资源工厂(比如 Supplier<? extends Tile>)。资源工厂是用来动态生成资源的工具。Mosaic 类有一个方法,比如 create,它会通过调用资源工厂的 get() 方法来生成具体的资源对象。这些资源对象可能是 Tile 的子类,比如 CeramicTileStoneTile。通过使用不同的资源工厂(比如 Supplier<CeramicTile>Supplier<StoneTile>),可以让 Mosaic 类处理不同类型的资源,而不需要直接关心具体的资源类型。代码如下

// 抽象资源
abstract class Tile {
    abstract String getMaterial();
}

// 具体资源
class CeramicTile extends Tile {
    @Override
    String getMaterial() {
        return "Ceramic";
    }
}

class StoneTile extends Tile {
    @Override
    String getMaterial() {
        return "Stone";
    }
}

// Mosaic 类
class Mosaic {
    private final Supplier<? extends Tile> tileFactory;

    // 构造器接受资源工厂
    public Mosaic(Supplier<? extends Tile> tileFactory) {
        this.tileFactory = tileFactory;
    }

    // 使用资源工厂创建资源
    public Tile createTile() {
        return tileFactory.get();
    }
}

// 主程序
public class Main {
    public static void main(String[] args) {
        // 传入 CeramicTile 工厂
        Mosaic ceramicMosaic = new Mosaic(CeramicTile::new);
        System.out.println("Created tile: " + ceramicMosaic.createTile().getMaterial());

        // 传入 StoneTile 工厂
        Mosaic stoneMosaic = new Mosaic(StoneTile::new);
        System.out.println("Created tile: " + stoneMosaic.createTile().getMaterial());
    }
}

依赖注入的缺点

虽然依赖注入提升了灵活性和测试性。但可能会让包含千个依赖项的大项目变得杂乱无章。因此可以通过依赖注入框架来避免这种混乱。比如Spring,Guice,Dagger。

总结

对于依赖一个或多个底层资源,而且资源的行为会对其行为造成影响的类,不要使用Singleton或者静态工具类,也不要让该类直接创建这些资源。相反应该将资源或者创建的资源工厂传递给构造器(或静态工厂,生成器)。这就是所谓的依赖注入,将极大的提升类的灵活性、可复用性和可测试性。


网站公告

今日签到

点亮在社区的每一天
去签到