这是 2020 年 3 月整理的设计模式专题第一篇。工厂模式是创建型模式里最常被问到的,面试几乎必考”三种工厂的区别”。本文以慕课网课程为例,从最朴素的简单工厂出发,一步步演进到工厂方法、抽象工厂,讲清每种模式的定位与取舍。
代码前置条件
以慕课网课程为例,一门课程需要有视频。抽象类 Video,其有两个实现类 JavaVideo 和 PythonVideo:
public abstract class Video {
public abstract void produce();
}
public class JavaVideo extends Video {
@Override
public void produce() {
System.out.println("录制 Java 课程视频");
}
}
public class PythonVideo extends Video {
@Override
public void produce() {
System.out.println("录制 Python 课程视频");
}
}
简单工厂(Simple Factory)
简单说明
简单工厂并不属于 23 种设计模式,而是一种编码风格,简单的实现代码。
public class SimpleVideoFactory1 {
public static Video getVideo(int type) {
if (type == 1) {
return new JavaVideo();
} else if (type == 2) {
return new PythonVideo();
} else {
return null;
}
}
}
问题:上面的工厂,在 Video 有新的子类(比如 FeVideo)时,需要修改工厂的代码,不符合开闭原则。
优化:用 Class 替代 Type
public class SimpleVideoFactory2 {
public static <T extends Video> Video getVideo(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
局限点:这种方式的局限在于不容易创建复杂对象——如果对象构造需要传参、依赖注入或复杂的初始化流程,newInstance() 就无能为力了。
工厂方法(Factory Method)
简单说明
工厂方法是 23 种设计模式之一,主要定义:
定义一个创建对象的接口,让实现这个接口的类来决定实例化哪个类,把类的实例化推迟到子类中。
- 在创建一个类需要非常复杂的过程,或需要大量冗余代码时,可以使用此方法;
- 工厂方法让客户端不依赖于产品实例如何被创建、实现等细节;
- 优点:符合开闭原则;
- 缺点:类的个数容易过多,引起类爆炸,也增加了系统的抽象性和理解难度。
实现
声明一个创建产品的接口类(或者抽象类),再为每个产品创建单独的工厂。使用时根据实例化的具体工厂生产特定产品。需要扩展产品时,额外创建新的产品工厂即可。
/**
* 描述:工厂方法抽象类
*/
public abstract class FactoryMethodVideoFactory {
public abstract Video getVideo();
}
/**
* 描述:工厂方法实现类:Java 视频工厂
*/
public class FactoryMethodJavaVideoFactory extends FactoryMethodVideoFactory {
@Override
public Video getVideo() {
return new JavaVideo();
}
}
// 使用:想创建哪种视频,就实例化哪个工厂
FactoryMethodVideoFactory factory = new FactoryMethodJavaVideoFactory();
Video video = factory.getVideo();
video.produce();
开闭原则体现:新增一种视频时,只需要新增 FeVideo + FactoryMethodFeVideoFactory,不改动已有代码。
抽象工厂(Abstract Factory)
简单说明
抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,能将一个系列的产品族统一到一起创建,在一定程度上解决工厂方法模式中类爆炸的情况。但是在产品组扩展新的产品时比较困难,需要修改抽象工厂的接口。
前置条件升级
以前的课程,只有一个 Video 对象即可。随着业务发展,现在要求有 Video 和手记 Article:
- 如果按照工厂方法的方式扩展,就要继续创建
JavaArticleFactory和PythonArticleFactory; - 随着业务发展,每多一种课程(比如前端课程),就要引入其
Video、Article实现(这两个必不可少),还要引入响应的 Factory; - 而且在拼装课程时还有风险拼装错误(使用了
JavaVideo和PythonArticle拼成一个课程)。
此时引入抽象工厂模式:抽象工厂可以创建同一个产品组的产品——例如 java 相关的 Video 和 Article,就是一个产品族。
实现
/**
* 描述:定义一个课程的接口,有获取课程视频和手记的方法
*/
public interface CourseFactory {
Video getVideo();
Article getArticle();
}
/**
* 描述:Java 课程工厂(Java 产品族)
*/
public class JavaCourseFactory implements CourseFactory {
@Override
public Video getVideo() {
return new JavaVideo();
}
@Override
public Article getArticle() {
return new JavaArticle();
}
}
/**
* 描述:Python 课程工厂(Python 产品族)
*/
public class PythonCourseFactory implements CourseFactory {
@Override
public Video getVideo() {
return new PythonVideo();
}
@Override
public Article getArticle() {
return new PythonArticle();
}
}
// 使用:产品族被"绑定",不会出现 JavaVideo + PythonArticle 拼错的情况
CourseFactory factory = new JavaCourseFactory();
Video video = factory.getVideo();
Article article = factory.getArticle();
产品族 vs 产品等级
产品等级结构(同一种类的不同实现)
Video ←—————— Article
/ \ / \
JavaVideo PythonVideo JavaArticle PythonArticle
\______ Java 产品族 ______/ ← 抽象工厂按"族"创建
\______ Python 产品族 ______/
- 产品族:同一工厂创建的、有相关性的一组产品(Java 的 Video + Article);
- 产品等级结构:同一种产品的不同实现(JavaVideo / PythonVideo);
- 抽象工厂按产品族创建,天然保证族内产品匹配,杜绝拼装错误;
- 扩展难点:若要在产品等级结构上新增一类产品(如新增
Note),需要修改CourseFactory接口及其所有实现——不符合开闭原则,这也是抽象工厂的主要缺点。
三种工厂对比
| 模式 | 核心思想 | 开闭原则 | 优点 | 缺点 |
|---|---|---|---|---|
| 简单工厂 | 一个工厂方法 + if/else 判断类型 | 不满足(新增产品要改工厂) | 实现简单,代码量少 | 扩展需修改工厂;不适合复杂对象创建 |
| 工厂方法 | 每个产品一个工厂,创建推迟到子类 | 满足(新增产品只加新工厂) | 符合开闭原则,职责单一 | 类数量膨胀(类爆炸),理解难度增加 |
| 抽象工厂 | 一个工厂创建一族相关产品 | 对产品族满足,对新增产品线不满足 | 产品族统一创建,避免拼错 | 新增产品线需改接口及所有实现 |
选型思路:
- 只有一种产品、产品线简单 → 简单工厂够用;
- 产品种类会持续扩展、希望符合开闭原则 → 工厂方法;
- 产品之间存在相关性、需要保证”族”内配套 → 抽象工厂。
现实中的应用
| 场景 | 对应模式 |
|---|---|
java.util.Calendar.getInstance() | 工厂方法(不同地区返回不同实例) |
java.util.logging.Logger / SLF4J LoggerFactory | 工厂方法 |
| Spring IOC 容器创建 Bean | 工厂方法(BeanFactory) |
BeanFactory 的 getBean 按类型/名字创建 | 简单工厂 + 反射 |
MyBatis SqlSessionFactory | 工厂方法 |
| 跨平台 UI 组件(Windows/Mac 各自一套按钮+文本框) | 抽象工厂(产品族 = 平台) |
小结
- 简单工厂:一个类集中创建对象,用 if/else 或反射区分类型,不属于 23 种设计模式,扩展需改代码。
- 工厂方法:每个产品对应一个工厂,创建逻辑推迟到子类,符合开闭原则,代价是类爆炸。
- 抽象工厂:一个工厂创建一整个产品族,保证族内产品配套、避免拼装错误,但新增产品线时要改接口。
- 三种工厂是从”简单到复杂”的演进:简单工厂解决”怎么创建”,工厂方法解决”如何扩展”,抽象工厂解决”如何保证配套”。