设计模式之工厂方法模式(Factory Method Pattern)
工厂方法模式(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):实现工厂方法,返回具体的实例。
classDiagram
%% 1. 抽象产品
class Product {
<<interface>>
+doSomething() void
}
%% 2. 具体产品
class ConcreteProduct {
+doSomething() void
}
%% 3. 抽象工厂
class AbstractFactory {
<<interface>>
+factoryMethod() Product
}
%% 4. 具体工厂
class ConcreteFactory {
+factoryMethod() Product
}
%% 关系连线
Product <|.. ConcreteProduct : 实现 (Implements)
AbstractFactory <|.. ConcreteFactory : 实现 (Implements)
ConcreteFactory ..> ConcreteProduct : 实例化 (Creates)
AbstractFactory ..> Product : 依赖 (Depends)
三、 场景推演:手机代工厂
基础代码实现(以手机代工厂为例):
1 | // 1. 抽象产品:手机 |
客户端调用:
1 | public class Client { |
四、实战案例:跨平台对话框按钮
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 面条代码类图
classDiagram
class Client {
+main(args: String[]) void
}
class Dialog {
+renderWindow() void
+showAlert() void
}
class HtmlButtons {
+render() void
+onClick() void
}
class WindowsButtons {
+render() void
+onClick() void
}
class MacButtons {
+render() void
+onClick() void
}
class LinuxButtons {
+render() void
+onClick() void
}
Client --> Dialog : 调用
Dialog ..> HtmlButtons : 直接依赖
Dialog ..> WindowsButtons : 直接依赖
Dialog ..> MacButtons : 直接依赖
Dialog ..> LinuxButtons : 直接依赖
style Client fill:#ffa,stroke:#333,stroke-width:2px
style Dialog fill:#f66,stroke:#333,stroke-width:2px
4.3.2 工厂方法类图
classDiagram
class IButton {
<<interface>>
+render() void
+onClick() void
}
class HtmlButtons {
+render() void
+onClick() void
}
class WindowsButtons {
-panel: JPanel
-frame: JFrame
-button: JButton
+render() void
+onClick() void
}
class DialogFactory {
<<abstract>>
+renderWindow() void
+createButton()* IButton
}
class HtmlDialogFactory {
+createButton() IButton
}
class WindowsDialogFactory {
+createButton() IButton
}
class Client {
-dialogFactory: DialogFactory
+config() void
+runBusinessLogic() void
}
IButton <|.. HtmlButtons : 实现
IButton <|.. WindowsButtons : 实现
DialogFactory <|-- HtmlDialogFactory : 继承
DialogFactory <|-- WindowsDialogFactory : 继承
DialogFactory ..> IButton : 创建
HtmlDialogFactory ..> HtmlButtons : 创建
WindowsDialogFactory ..> WindowsButtons : 创建
Client --> DialogFactory : 依赖
4.4 时序图
4.4.2 面条代码类图
sequenceDiagram
participant Client as 客户端
participant Dialog as Dialog(上帝类)
participant WinBtn as WindowsButtons
participant HtmlBtn as HtmlButtons
Client->>Dialog: renderWindow()
Dialog->>Dialog: 获取 os.name
alt os == "Windows 11"
Dialog->>WinBtn: new WindowsButtons()
Dialog->>WinBtn: render()
else os == "Mac OS X"
Dialog->>Dialog: new MacButtons()...
else 其他
Dialog->>HtmlBtn: new HtmlButtons()
Dialog->>HtmlBtn: render()
end
Note over Dialog: 所有判断逻辑<br/>都堆在这一个方法里
4.4.2 工厂方法类图
sequenceDiagram
participant Client as 客户端(DialogFactoryTest)
participant Factory as DialogFactory
participant ConcreteFactory as WindowsDialogFactory
participant Product as WindowsButtons
Client->>Client: config() 检测操作系统
alt Windows 环境
Client->>ConcreteFactory: new WindowsDialogFactory()
else 非 Windows 环境
Client->>ConcreteFactory: new HtmlDialogFactory()
end
Client->>Factory: renderWindow()
Factory->>ConcreteFactory: createButton() [工厂方法]
ConcreteFactory-->>Factory: 返回 IButton 实例
Factory->>Product: render()
Product->>Product: onClick()
Product-->>Factory: 渲染完成
Factory-->>Client: 业务执行完成
4.5 代码比较
4.5.1 工厂方法代码
代码结构
1 | com.likerhood.design |
产品接口 IButton:定义按钮的统一行为契约
1 | public interface IButton { |
抽象工厂 DialogFactory:定义工厂方法 + 业务模板
1 | public abstract class DialogFactory { |
具体工厂:各自创建对应平台的按钮
1 | // HTML 工厂 |
客户端:运行时根据环境选择工厂
1 | public static void config() { |
4.5.2 面条代码(if-else 硬编码)
如果不用工厂方法模式,最直接的做法就是把所有创建逻辑和业务逻辑混在一起,用 if-else 判断环境后直接 new 具体对象:
1 | public class Dialog { |
客户端调用:
1 | public class Client { |
看起来代码更少,但问题巨大。
如果需求变化了呢?
假设现在要新增 Mac 按钮、Linux 按钮,代码会变成这样:
1 | public class Dialog { |
每新增一个平台,所有用到按钮的地方都要改,if-else 到处重复。
总结
| 角色 | 本案例对应类 | 职责 |
|---|---|---|
| 抽象产品 | IButton |
定义产品的统一接口 |
| 具体产品 | HtmlButtons / WindowsButtons |
实现不同平台的按钮逻辑 |
| 抽象工厂 | DialogFactory |
声明工厂方法 + 封装业务模板 |
| 具体工厂 | HtmlDialogFactory / WindowsDialogFactory |
实现工厂方法,创建对应产品 |
| 客户端 | DialogFactoryTest |
选择工厂,调用业务方法 |
工厂方法模式的关键点:
DialogFactory.renderWindow()中调用了抽象方法createButton(),具体创建哪种按钮由子类工厂决定——这就是”将实例化延迟到子类“的精髓。客户端只与
DialogFactory和IButton两个抽象打交道,完全不感知具体产品类的存在,实现了真正的面向接口编程。