好像简单工厂模式已经不被纳入GOF23种的其中一种了吧,或者说作为了工厂模式的一个特例。那本文就介绍工厂模式和抽象工厂模式这两种常用的吧。本文做记录的原因除了工厂模式估计是代码中比较常见的设计模式之外,还有一个原因就是要分清什么时候要用工厂模式,什么时候要new一个对象。
1、工厂模式
设计模式主要分为三大类,分别是创建型、结构型和行为,其中工厂模式数据属于创建型。其允许由父类定义创建对象的接口,然后由具体的子类去决定实例化的对象类型。
工厂模式最主要的应用场景就是想要把对象的创建和业务逻辑互相剥离,使用特殊的方法代替对于对象构造函数的直接调用 (即使用 new
运算符,但是在 工厂的方法内部还是用new运算法,你也可以认为就是给换了个地方调用)。举个栗子,在Netty中的ChannelFactory就是工厂模式的一个使用。交由不同的子类去实例化不同类型的channel。
public interface ChannelFactory<T extends Channel> extends io.netty.bootstrap.ChannelFactory<T> {
/**
* Creates a new channel.
*/
@Override
T newChannel();
}
// 默认的工厂实现类ReflectiveChannelFactory
// 基于传递不同类型的channel,通过反射的方式创建
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Constructor<? extends T> constructor;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
ObjectUtil.checkNotNull(clazz, "clazz");
try {
this.constructor = clazz.getConstructor();
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
" does not have a public non-arg constructor", e);
}
}
@Override
public T newChannel() {
try {
return constructor.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
}
}
...
}
// 还有一些其他的工厂类实现
public static final IoTransport DEFAULT = new IoTransport(new NioEventLoopGroup(1).next(),
new ChannelFactory<SocketChannel>() {
@Override
public SocketChannel newChannel() {
return new NioSocketChannel();
}
,
new ChannelFactory<DatagramChannel>() {
@Override
public DatagramChannel newChannel() {
return new NioDatagramChannel();
}
);
如果不按照上面的方法去设计的,大概你的业务逻辑就是会出现大量的swith-case分支语法了。嘛其实这也是推荐使用工厂模式的一个原因,最大限度的通过扩展而不是修改原有的代码(开闭原则?)。除此之外,工厂模式的一个优点就是将对象创建的逻辑集中在一处,可别小瞧这个改动,当你这个对象是核心的时候,有可能你会在业务的多处出现这个对象的创建,这对于一丁点关于这个对象的修改都有可能需要遍历整个程序。还有一个常见的使用案例是,使用工厂模式去缓存和复用对象,常见的有数据库连接和文件系统等。
相反,如果你这个对象实现不多的话,就没必要引入工厂模式。不然就真的是装逼行为。上面的工厂模式只是介绍了创建对象的方式,但是要与你当前的业务逻辑挂上钩,单纯的靠工厂模式还不够,常见的还会结合SPI、配置文件等结合当前的环境初始化对应的对象。常见的例子就有Dubbo,会根据你当前的url前缀,初始化连接和注册中心等。这里就不详说了。
总结一下,使用工厂模式的应用场景推荐:
- 当你想把对象的创建根据当前业务环境决定,又不想引入大量的swith-case避免下一次扩展类型时还要去修改代码;
- 将核心对象的创建集中在单独一处,避免程序多次出现和修改;
- 复用现有对象来节省系统资源, 而不是每次都重新创建对象。
2、抽象工厂模式
抽象工厂模式算是工厂模式的一种拓展,其目的就是为了生成在业务逻辑中有相关联性的一组对象,让原本的工厂模式突破单一对象的局限。以Debezium的ChangeEventSourceFactory为例,它选择使用抽象工厂模式的方式去实现,就是为了在不同的数据源,提供全量流和增量流两个源。
public interface ChangeEventSourceFactory<P extends Partition, O extends OffsetContext> {
SnapshotChangeEventSource<P, O> getSnapshotChangeEventSource(SnapshotProgressListener<P> snapshotProgressListener, NotificationService<P, O> notificationService);
StreamingChangeEventSource<P, O> getStreamingChangeEventSource();
例如当我需要提供一个关于MySQL的增量流和全量流时,只需要实现MySQLChangeEventSourceFactory,返回关于MySQL的SnapshotChangeEventSource和StreamingChangeEventSource即可。
写到一半的时候突然才想到,工厂模式属于是一个产品和对应多个不同的风格或者系列实现类,也就是1:N的关系。抽象工厂模式则是多个产品和多个不同的风格的交互,也就是M:N的关系,不过前提是一个工厂只能设计一种风格的产品(总不能在代码中出现哥特式的工厂生产出美式风格的家具吧😧)。所以抽象工厂模式的主要使用场景可以归纳以下:
-
如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体系列进行构建, 在这种情况下, 你可以使用抽象工厂。