工厂方法模式(Factory Method Pattern)
前言
简单工厂模式虽然初步实现了对象创建与使用的分离,但其内部臃肿的 if-else 逻辑却成为了系统扩展时的痛点,严重违背了开闭原则(OCP)。
如何优雅地打破这种"上帝类"的束缚?工厂方法模式(Factory Method Pattern) 给出了标准答案:利用多态,将对象的实例化彻底延迟到子类中进行。
参考博客:工厂方法设计模式
一、核心定义
在简单工厂模式 中,所有的创建逻辑都集中在一个"上帝工厂类"里(充满了 if-else 或 switch-case)。
痛点 :一旦新增产品(比如新增华为手机),就必须修改这个工厂类的源代码,这严重违背了开闭原则(Open-Closed Principle, OCP)------对扩展开放,对修改封闭。
工厂方法模式 通过"多态"解决了这个问题:将对象的实例化推迟到子类中进行。
核心思想是:定义一个创建对象的接口(抽象工厂),但让实现这个接口的类(具体工厂)来决定实例化哪个类(具体产品)。
核心思想 :不直接
new对象,而是通过工厂子类来决定创建哪种产品,实现创建与使用的解耦。
| 作用 | 说明 |
|---|---|
| 解耦 | 客户端代码只依赖抽象接口,不依赖具体产品类,降低耦合度 |
| 开闭原则 | 新增产品只需新增子类工厂,不需要修改已有代码 |
| 单一职责 | 将产品创建逻辑集中在工厂类中,业务代码不关心对象如何创建 |
| 灵活扩展 | 支持运行时动态切换产品族,适应不同平台/环境的需求 |
适用场景
- 不确定运行时需要哪种具体对象
- 希望通过子类来扩展产品创建逻辑
- 需要将创建逻辑与业务逻辑分离
二、标准体系结构图 (UML)
工厂方法模式包含四个核心角色:
- 抽象产品(Product):定义产品的接口规范。
- 具体产品(Concrete Product):实现抽象产品接口的具体类。
- 抽象工厂(Abstract Factory):声明返回抽象产品对象的工厂方法。
- 具体工厂(Concrete Factory):实现工厂方法,返回具体的实例。
实现 (Implements)
实现 (Implements)
实例化 (Creates)
依赖 (Depends)
<<interface>>
Product
+doSomething() : void
ConcreteProduct
+doSomething() : void
<<interface>>
AbstractFactory
+factoryMethod() : Product
ConcreteFactory
+factoryMethod() : Product
三、 场景推演:手机代工厂
基础代码实现(以手机代工厂为例):
Java
// 1. 抽象产品:手机
public interface Phone {
void make();
}
// 2. 具体产品:苹果手机与小米手机
public class IPhone implements Phone {
@Override
public void make() { System.out.println("组装一台 iPhone"); }
}
public class MiPhone implements Phone {
@Override
public void make() { System.out.println("组装一台 小米手机"); }
}
// 3. 抽象工厂:手机制造标准
public interface PhoneFactory {
Phone createPhone(); // 将生产细节推迟到具体工厂
}
// 4. 具体工厂:专厂专办
public class AppleFactory implements PhoneFactory {
@Override
public Phone createPhone() { return new IPhone(); }
}
public class XiaomiFactory implements PhoneFactory {
@Override
public Phone createPhone() { return new MiPhone(); }
}
客户端调用:
Java
public class Client {
public static void main(String[] args) {
// 想买小米手机,找小米专属工厂
PhoneFactory xiaomiFactory = new XiaomiFactory();
Phone myPhone = xiaomiFactory.createPhone();
myPhone.make();
// 【优势体现】:如果明天要造华为手机,只需新增 HuaweiPhone 和 HuaweiFactory 类,
// 原有代码一行都不用改,完美符合开闭原则!
}
}
四、实战案例:跨平台对话框按钮
4.1 需求分析
业务场景:开发一个跨平台的对话框组件,根据当前运行环境(Windows / Web)自动渲染不同风格的按钮:
- Windows 环境:渲染原生 Swing 窗口按钮(带 GUI 面板、标签、退出按钮)
- Web 环境 :渲染 HTML 按钮(输出
<button>标签)
痛点 :如果用 if-else 在业务代码里判断环境并直接 new 不同的按钮对象,会导致:
- 客户端代码与所有具体按钮类强耦合
- 每新增一种平台按钮就要修改业务逻辑,违反开闭原则
- 创建逻辑散落在各处,难以维护
解决方案 :使用工厂方法模式,将按钮的创建委托给各平台的工厂子类。
**实战代码:**https://github.com/likerhood/CodeDesignWork
运行结果:

4.2 架构图
4.2.1 面条代码架构图

4.2.2 工厂方法架构图

4.3 类图对比
4.3.1 面条代码类图
调用
直接依赖
直接依赖
直接依赖
直接依赖
Client
+main(args: String[]) : void
Dialog
+renderWindow() : void
+showAlert() : void
HtmlButtons
+render() : void
+onClick() : void
WindowsButtons
+render() : void
+onClick() : void
MacButtons
+render() : void
+onClick() : void
LinuxButtons
+render() : void
+onClick() : void
4.3.2 工厂方法类图
实现
实现
继承
继承
创建
创建
创建
依赖
<<interface>>
IButton
+render() : void
+onClick() : void
HtmlButtons
+render() : void
+onClick() : void
WindowsButtons
-panel: JPanel
-frame: JFrame
-button: JButton
+render() : void
+onClick() : void
<<abstract>>
DialogFactory
+renderWindow() : void
+createButton() : IButton
HtmlDialogFactory
+createButton() : IButton
WindowsDialogFactory
+createButton() : IButton
Client
-dialogFactory: DialogFactory
+config() : void
+runBusinessLogic() : void
4.4 时序图
4.4.2 面条代码类图
HtmlButtons WindowsButtons Dialog(上帝类) 客户端 HtmlButtons WindowsButtons Dialog(上帝类) 客户端 alt [os == "Windows 11"] [os == "Mac OS X"] [其他] 所有判断逻辑 都堆在这一个方法里 renderWindow() 获取 os.name new WindowsButtons() render() new MacButtons()... new HtmlButtons() render()
4.4.2 工厂方法类图
WindowsButtons WindowsDialogFactory DialogFactory 客户端(DialogFactoryTest) WindowsButtons WindowsDialogFactory DialogFactory 客户端(DialogFactoryTest) alt [Windows 环境] [非 Windows 环境] config() 检测操作系统 new WindowsDialogFactory() new HtmlDialogFactory() renderWindow() createButton() [工厂方法] 返回 IButton 实例 render() onClick() 渲染完成 业务执行完成
4.5 代码比较
4.5.1 工厂方法代码
代码结构
com.likerhood.design
├── buttons/ # 产品层
│ ├── IButton.java # 产品接口
│ └── imp/
│ ├── HtmlButtons.java # 具体产品 - HTML按钮
│ └── WindowsButtons.java # 具体产品 - Windows按钮
├── factory/ # 工厂层
│ ├── DialogFactory.java # 抽象工厂(含工厂方法)
│ └── imp/
│ ├── HtmlDialogFactory.java # 具体工厂 - 生产HTML按钮
│ └── WindowsDialogFactory.java # 具体工厂 - 生产Windows按钮
产品接口 IButton:定义按钮的统一行为契约
java
public interface IButton {
void render(); // 渲染按钮
void onClick(); // 点击事件
}
抽象工厂 DialogFactory:定义工厂方法 + 业务模板
java
public abstract class DialogFactory {
// 业务方法:渲染对话窗口(模板方法)
public void renderWindow() {
IButton okButton = createButton(); // 调用工厂方法
okButton.render();
}
// 工厂方法:由子类实现,决定创建哪种按钮
public abstract IButton createButton();
}
具体工厂:各自创建对应平台的按钮
java
// HTML 工厂
public class HtmlDialogFactory extends DialogFactory {
public IButton createButton() {
return new HtmlButtons();
}
}
// Windows 工厂
public class WindowsDialogFactory extends DialogFactory {
public IButton createButton() {
return new WindowsButtons();
}
}
客户端:运行时根据环境选择工厂
java
public static void config() {
if (System.getProperty("os.name").equals("Windows 11")) {
dialogFactory = new WindowsDialogFactory();
} else {
dialogFactory = new HtmlDialogFactory();
}
}
public static void runBusinessLogic() {
dialogFactory.renderWindow(); // 客户端只依赖抽象,不关心具体产品
}
4.5.2 面条代码(if-else 硬编码)
如果不用工厂方法模式,最直接的做法就是把所有创建逻辑和业务逻辑混在一起 ,用 if-else 判断环境后直接 new 具体对象:
java
public class Dialog {
public void renderWindow() {
String os = System.getProperty("os.name");
if (os.equals("Windows 11")) {
// 直接 new 具体产品类
WindowsButtons button = new WindowsButtons();
button.render();
} else {
// 直接 new 另一个具体产品类
HtmlButtons button = new HtmlButtons();
button.render();
}
}
}
客户端调用:
java
public class Client {
public static void main(String[] args) {
Dialog dialog = new Dialog();
dialog.renderWindow();
}
}
看起来代码更少,但问题巨大。
如果需求变化了呢?
假设现在要新增 Mac 按钮 、Linux 按钮,代码会变成这样:
java
public class Dialog {
public void renderWindow() {
String os = System.getProperty("os.name");
if (os.equals("Windows 11")) {
WindowsButtons button = new WindowsButtons();
button.render();
} else if (os.equals("Mac OS X")) {
MacButtons button = new MacButtons(); // 改动1
button.render();
} else if (os.equals("Linux")) {
LinuxButtons button = new LinuxButtons(); // 改动2
button.render();
} else {
HtmlButtons button = new HtmlButtons();
button.render();
}
}
// 假设还有另一个业务方法也需要按钮...
public void showAlert() {
String os = System.getProperty("os.name");
// 又要重复一遍完全相同的 if-else!
if (os.equals("Windows 11")) {
WindowsButtons button = new WindowsButtons();
button.onClick();
} else if (os.equals("Mac OS X")) {
MacButtons button = new MacButtons();
button.onClick();
} else if (os.equals("Linux")) {
LinuxButtons button = new LinuxButtons();
button.onClick();
} else {
HtmlButtons button = new HtmlButtons();
button.onClick();
}
}
}
每新增一个平台,所有用到按钮的地方都要改,if-else 到处重复。
总结
| 角色 | 本案例对应类 | 职责 |
|---|---|---|
| 抽象产品 | IButton |
定义产品的统一接口 |
| 具体产品 | HtmlButtons / WindowsButtons |
实现不同平台的按钮逻辑 |
| 抽象工厂 | DialogFactory |
声明工厂方法 + 封装业务模板 |
| 具体工厂 | HtmlDialogFactory / WindowsDialogFactory |
实现工厂方法,创建对应产品 |
| 客户端 | DialogFactoryTest |
选择工厂,调用业务方法 |
工厂方法模式的关键点:
-
DialogFactory.renderWindow()中调用了抽象方法createButton(),具体创建哪种按钮由子类工厂决定------这就是"将实例化延迟到子类"的精髓。 -
客户端只与
DialogFactory和IButton两个抽象打交道,完全不感知具体产品类的存在,实现了真正的面向接口编程。