创建和使用分离——工厂方法模式
1. 定义
工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。
和简单工厂模式一样也是创建和使用分离。
区别在于,简单工厂模式所有的产品都由一个工厂生产。这样的话导致产品和工厂耦合,新增和减少产品都要修改工厂类,不符合开闭原则。
工厂方法模式一个产品对应一个工厂,不同的产品提供不同的工厂。这样新产品只需要创建新的工厂,易于扩展,符合开闭原则。
2. 设计
工厂模式主要角色:
- 抽象产品,定义产品的基本接口。
- 具体产品,实现产品的接口。
- 抽象工厂,定义工厂方法,用来返回一个产品。
- 具体工厂,实现工厂方法,返回具体的产品。
可以看到,这里和简单工厂的区别在于,不仅产品有一个抽象类,工厂也增加了一个抽象类。
类图如下:
工厂方法模式-类图抽象产品:
public interface IProduct {
void sayHello();
}
抽象工厂:
public interface IFactory {
IProduct getProduct();
}
具体产品 A:
public class ProductA implements IProduct {
public void sayHello() {
System.out.println("A hello.");
}
}
具体工厂 A,用来生产具体产品 A:
public class FactoryA implements IFactory {
public IProduct getProduct() {
return new ProductA();
}
}
使用的地方,需要产品 A,使用工厂 A 来获取。需要产品 B,使用工厂 B 来获取:
public class TestFactoryMethod {
public static void main(String[] args) {
// 创建产品 A
IFactory factory = new FactoryA();
IProduct product = factory.getProduct();
product.sayHello();
// 创建产品 B
factory = new FactoryB();
product = factory.getProduct();
product.sayHello();
}
}
如果需要增加产品 C,不用修改其他工厂类,创建具体产品 C 和工厂 C 即可,很容易进行扩展。
3. 应用
上面提供的模型比较简单。
工厂模式往往应用于复杂对象的创建。在创建对象的同时,还可以进行对象的初始化、资源和环境的配置等。
工厂方法模式的工厂,还可以使用配置文件来声明。
3.1. Spring 的 FactoryBean
Spring 的 FactoryBean 是工厂模式的典型应用。
定义如下:
public interface FactoryBean<T> {
...
T getObject() throws Exception;
...
}
工厂方法为 getObject
。
对于复杂对象的构建,如果需要读取资源、配置,需要进行特殊的初始化等,可以实现对应的工厂。比如连接数据库、连接网络、创建对象池等等。
很多开源框架会使用 FactoryBean 来生成对应的实例。
Spring 使用工厂来生成对应的对象,这个解决了 Spring 对复杂对象的构建难题。
比如 MyBatis 框架的 SqlSessionFactoryBean
,用来创建 SqlSessionFactory
对象。整个 SqlSessionFactory
的创建非常复杂,需要读取资源文件和配置。SqlSessionFactoryBean
使得这个复杂的创建流程对使用者透明。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
...
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
...
this.sqlSessionFactory = buildSqlSessionFactory();
}
...
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
..
return this.sqlSessionFactoryBuilder.build(configuration);
}
...
}
3.2. Dubbo 的 RegistryFactory
Dubbo 有一个很强大的地方,就是可以交给使用者自由地选择组件。
比如:
- 注册中心。
- 序列化协议。
- 缓存方式。
等等
具体的方式是:
- 先制定统一的接口,屏蔽不同组件的。
- 由配置文件决定使用具体的实现。可以选择 Dubbo 默认提供的,也可以自定义实现。
- 运行时 Dubbo 会通过 ExtensionLoader 读取配置文件,动态载入所需要的实现类。
Dubbo 的 RegistryFactory 是注册中心的生产工厂,也是框架的一个 SPI 扩展组件。
创建注册中心时,需要将复杂的创建过程和使用进行隔离,然后又希望能够生产不同的产品,在添加新产品的时候,不想修改到旧代码。
这里应用了工厂方法模式,用一句话描述工厂方法,就是创建和使用分离+一种产品。
抽象产品 Registry。
// 注册中心 = 节点 + 服务
public interface Registry extends Node, RegistryService {
}
// 服务节点抽象
public interface Node {
URL getUrl();
boolean isAvailable();
void destroy();
}
// 注册服务抽象
public interface RegistryService {
void register(URL url);
void unregister(URL url);
void subscribe(URL url, NotifyListener listener);
void unsubscribe(URL url, NotifyListener listener);
List<URL> lookup(URL url);
}
抽象工厂 RegistryFactory:
@SPI("dubbo")
public interface RegistryFactory {
@Adaptive({"protocol"})
Registry getRegistry(URL url);
}
具体产品:
- DubboRegistry,Dubbo 自己实现的注册中心。
- MulticastRegistry,无中心节点的注册中心。
- RedisRegistry,Redis 实现的注册中心。
- ZookeeperRegistry,基于 Zookeeper 的注册中心。
具体工厂:
- DubboRegistryFactory,生产 DubboRegistry。
- MulticastRegistryFactory,生产 MulticastRegistry。
- RedisRegistryFactory,生产 RedisRegistry。
- ZookeeperRegistryFactory,生产 ZookeeperRegistry。
通过工厂方法,屏蔽了注册中心复杂的构建细节。然后在配置文件指定具体的工厂,通过 SPI 机制加载,可以让 Dubbo 动态选择具体的注册中心,而不用改到源码,扩展性极强。
Dubbo Registryfactory3.3 其他
还有很多框架使用到工厂方法,这里举例:
- Dubbo 的 CacheFactory,缓存工厂。
- Dubbo 的 MonitorFactory,监视器工厂。
4. 特点
4.1. 优势
- 创建和使用分离:使用者无需关系复杂的创建细节。
- 工厂决定产品:工厂可以自主选择希望返回的产品。
- 易于修改:修改产品和工厂不影响到其他的工厂。
- 易于扩展:新产品直接创建新的工厂类,扩展性好。
4.2. 缺点
-
类膨胀:如果产品数量多,对应的工厂也多,造成类的膨胀。
注意控制类的数量,如果产品数量繁多,想办法控制一下数量,或者直接使用简单工厂模式。
-
难理解:增加了一层抽象,会对理解带来难度。在进行反射等操作的复杂度会上升。