从策略模式到 SPI 机制

策略模式体现为 封装一系列的算法以供调用,使这些算法可以解耦独立于主业务之外而存在。而 SPI 扩展机制是 解耦程度更高 的策略模式。

策略模式

定义一种算法:

/**
 * 手机
 */
interface Phone {
    void call();
}

两个算法实现:

/**
 * 魅族手机
 */
@Slf4j
class MeizuPhone implements Phone {
    @Override
    public void call() {
        log.debug("No love, just fxxk off~");
    }
}
/**
 * 小米手机
 */
@Slf4j
public class MIPhone implements Phone {
    @Override
    public void call() {
        log.debug("Are you OK?");
    }
}

再来个工厂:

class PhoneFactory {
    private Map<String, Phone> map = new HashMap<>();

    private void init() {
        map.put("MI", new MIPhone());
        map.put("Meizu", new MeizuPhone());
    }
    
    public Phone getPhone(String type) {
        return map.get(type);
    }
}

之后在业务中就可以避免大坨的 If-Else 代码了:

PhoneFactory.getPhone("MI").call();

策略模式理解起来很简单,就是“封装逻辑”。

SPI 扩展机制

思考这样一个问题:如果我要换魅族手机,可能需要修改源码,这样毕竟不灵活,JDK 提供了一种 spi 扩展机制,开发者可以通过 java.util.ServiceLoader 的 load 方法获取 classpath:META-INF/services 下的信息,约定该文件夹下放入 spi 接口全类名的文件,如 com.cdf.spi.Phone,文件内容是本工程打包后对应的 spi 实现,比如 com.cdf.phone.MeizuPhone,具体我们看下例子:

MeizuPhone provider

MIPhone provider

只需要改变当前工程的依赖,由 meizu 换成 xiaomi:

<dependency>
    <groupId>com.cdf</groupId>
    <artifactId>meizu</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.cdf</groupId>
    <artifactId>xiaomi</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

即可改变程序中使用的具体实现:

ServiceLoader<Phone> phones = ServiceLoader.load(Phone.class);
for (Phone phone : phones) {
    phone.call();
}

result

这样的好处是,那些被封装的算法们又对业务本身更隔离了,在更改实现是不用一个个去找“Meizu”换成“MI”,spi 可以用在日志框架、JDBC 连接池等通用依赖中。

JDBC 以前的规范是需要通过 Class.forName() 手动加载类的,而新的 DriverManager 使用 SPI 实现,并不用开发者手动去加载驱动。