创建型模式:对象的诞生艺术
Vibe coding 让人人都能写代码,但设计模式决定了你写出来的东西能不能活过三个月。设计模式就是思考的工具。所以打算现在重新捡起来,写点博客记录
前言
写代码绕不开 new。但当系统复杂到一定程度,你会发现到处散落的 new 是个大麻烦:
- 改一个类的构造方式,满世界找
new的地方改 - 想根据条件创建不同对象,写出一堆
if-else - 对象创建过程太复杂,几十行初始化代码到处复制
创建型模式就是来治这些病的。一共 5 种,下面逐个拆解。
创建型模式关心的核心问题只有一个:对象怎么来的? 把"怎么创建对象"这件事封装起来,让使用者不需要知道细节,拿来就用。
一、单例模式(Singleton)
一句话
一个类在整个系统里只有一个实例,谁来要都给同一个。
生活中的例子
- 公司打印机:整个办公室共用一台打印机,不是每人发一台。你提交打印任务,和同事提交打印任务,都是发到同一台打印机。
- Windows 任务管理器:不管你按多少次 Ctrl+Shift+Esc,打开的都是同一个窗口,不会弹出来两个。
为什么需要它
有些东西天生只该有一份:
- 配置管理器:配置只需要读一次,全局共享
- 数据库连接池:连接是昂贵资源,不能每次
new一个 - 日志器:所有模块往同一个地方写日志
- 应用的全局缓存:数据只有一份,避免不同缓存实例数据不一致
如果不做限制,每个角落都 new 一份,轻则浪费内存,重则数据不一致。比如两个配置管理器实例,一个读了新配置,另一个还是老的——系统行为就乱套了。
示意图

代码实现
java实现(双重检查锁)
public class Singleton {
// volatile 防止指令重排序导致拿到半初始化的对象
private static volatile Singleton instance;
private Singleton() {} // 私有构造器,外面 new 不了
public static Singleton getInstance() {
if (instance == null) { // 第一次检查:避免每次都加锁
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查:防止两个线程同时通过第一层
instance = new Singleton();
}
}
}
return instance;
}
}
Python 实现
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
"""拦截对象创建过程,确保只有一个实例"""
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# 验证
a = Singleton()
b = Singleton()
print(a is b) # True —— 同一个对象
Python 线程安全版本:
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
if cls._instance is None: # 双重检查
cls._instance = super().__new__(cls)
return cls._instance
注意事项
| 问题 | 说明 | 解法 |
|---|---|---|
| 多线程安全 | 两个线程同时判断 instance 为空,创建出两个 | 加锁 / 双重检查 / 枚举 |
| 反射攻击 | Java 反射可以强行调用私有构造器 | 枚举实现可防;或在构造器里加判断 |
| 序列化问题 | 反序列化会绕过构造器创建新对象 | 加 readResolve() 方法 |
| 测试困难 | 全局状态难以 mock | 配合依赖注入框架使用 |
二、工厂方法模式(Factory Method)
一句话
定义一个创建对象的接口,让子类决定实例化哪个类。
生活中的例子
- 披萨连锁店:总部定义了"做披萨"的标准流程(揉面、放料、烤制),但北京店做的是宫保鸡丁披萨,纽约店做的是芝士披萨。"做什么口味"这个决定权交给各分店。
- 物流公司:客户只说"帮我发货",但走陆运还是空运还是海运,由物流公司根据情况决定用哪种运输工具。
- 手机工厂:你下单买一部手机,富士康帮你组装。同样的产线,装不同的零件就出不同型号。
为什么需要它
假设你在做一个文档导出功能:
// 这样写,每加一种格式就要改这段代码
if (type.equals("pdf")) {
return new PdfExporter();
} else if (type.equals("excel")) {
return new ExcelExporter();
} else if (type.equals("word")) {
return new WordExporter();
}
// 后来又加了 csv、html、markdown... 这段代码改到崩溃
每次新增格式都要回来改这段代码,改一次就有一次出 bug 的风险。这违反了开闭原则(对扩展开放,对修改关闭)。
结构图

工厂方法里有两条独立的"继承/实现链",这两条链是平行存在的,一条管"谁来造",一条管"造出来的东西长什么样"。
工厂:ExportFactory 是抽象类,子类继承它
产品:Exporter 是接口,实现类实现它
为什么这样设计?
ExportFactory 是抽象类而不是接口,因为它里面有公共代码可以复用:
// 这段逻辑所有工厂都一样,写在抽象类里复用
public void export(Data data, OutputStream out) {
Exporter exporter = createExporter(); // 调子类实现的方法
byte[] result = exporter.render(data);
out.write(result)
子类只需要实现 createExporter() 这一个方法就够了,其余逻辑继承父类。
Exporter 是接口,因为产品只需要约定"你必须有 render() 方法",没有任何公共代码可以共享,所以用接口更合适。
代码实现
java实现
// 抽象产品
public interface Exporter {
byte[] render(Data data);
}
// 具体产品
public class PdfExporter implements Exporter {
public byte[] render(Data data) {
// 生成 PDF 二进制
return pdfLibrary.generate(data);
}
}
public class ExcelExporter implements Exporter {
public byte[] render(Data data) {
// 生成 Excel 二进制
return excelLibrary.generate(data);
}
}
// 抽象工厂
public abstract class ExportFactory {
public abstract Exporter createExporter();
// 模板方法:使用产品,子类不需要管这部分
public void export(Data data, OutputStream out) throws IOException {
Exporter exporter = createExporter();
byte[] result = exporter.render(data);
out.write(result);
}
}
// 具体工厂
public class PdfExportFactory extends ExportFactory {
@Override
public Exporter createExporter() {
return new PdfExporter();
}
}
public class ExcelExportFactory extends ExportFactory {
@Override
public Exporter createExporter() {
return new ExcelExporter();
}
}
python实现
from abc import ABC, abstractmethod
# 抽象产品
class Exporter(ABC):
@abstractmethod
def render(self, data: dict) -> bytes:
pass
# 具体产品
class PdfExporter(Exporter):
def render(self, data: dict) -> bytes:
return f"PDF内容: {data}".encode()
class ExcelExporter(Exporter):
def render(self, data: dict) -> bytes:
return f"Excel内容: {data}".encode()
# 抽象工厂
class ExportFactory(ABC):
@abstractmethod
def create_exporter(self) -> Exporter:
pass
def export(self, data: dict, filepath: str):
"""模板方法:创建产品并使用它"""
exporter = self.create_exporter()
content = exporter.render(data)
with open(filepath, 'wb') as f:
f.write(content)
# 具体工厂
class PdfExportFactory(ExportFactory):
def create_exporter(self) -> Exporter:
return PdfExporter()
class ExcelExportFactory(ExportFactory):
def create_exporter(self) -> Exporter:
return ExcelExporter()
# 使用
factory = PdfExportFactory()
factory.export({"title": "报表"}, "report.pdf")
新增格式?写一个新的 Factory + Exporter 就行,原有代码一行不动。
三、抽象工厂模式(Abstract Factory)
一句话
提供一个接口,创建一系列相关的对象,而不指定具体类。
生活中的例子
- 宜家家具套装:你选了"北欧风格",那椅子、桌子、沙发、柜子全是北欧风的。你选了"中式风格",全套都是中式。不会出现北欧椅子配中式桌子的混搭。
- 手机生态:买了苹果手机,充电器是 Lightning/USB-C、耳机是 AirPods、手表是 Apple Watch——一整套生态。买安卓则是另一套。
- 游戏皮肤:选了"暗黑主题",角色、武器、坐骑、特效全部是暗黑风格的配套。
和工厂方法的区别
- 工厂方法:一个工厂造一种产品
- 抽象工厂:一个工厂造一族产品(多种产品配套)
| 工厂方法 | 抽象工厂 | |
|---|---|---|
| 一个工厂造几种产品 | 一种 | 一族(多种) |
| 解决什么问题 | 让子类决定造哪种产品 | 保证一整套产品风格一致 |
| 例子 | PdfFactory 只造 PdfExporter | MacFactory 造 Mac 全套 UI |
| 抽象的是什么 | 一个创建方法 | 一组创建方法 |
| 创建方法 | createExporter() — 只有一个 | createButton() + createInput() + createDialog() — 多个 |
"抽象工厂"的"抽象"更准确的理解是:把创建一整族相关产品这件事抽象成了一个接口,强调的是"族",而不只是单个产品。
结构图

代码实现
java实现
// ───────────────────────────────────────────
// 第一层:产品接口
// 定义每种 UI 组件"必须能做什么",不关心平台
// ───────────────────────────────────────────
// 按钮接口:所有平台的按钮都必须实现 render()
public interface Button { void render(); }
// 输入框接口:所有平台的输入框都必须实现 render()
public interface Input { void render(); }
// 弹窗接口:所有平台的弹窗都必须实现 show()
public interface Dialog { void show(String msg); }
// ───────────────────────────────────────────
// 第二层:Windows 产品族
// 三个类风格一致,都是 Windows 味儿
// ───────────────────────────────────────────
public class WindowsButton implements Button {
// 实现 Button 接口,渲染 Windows 风格的按钮
public void render() { System.out.println("Windows 风格按钮"); }
}
public class WindowsInput implements Input {
// 实现 Input 接口,渲染 Windows 风格的输入框
public void render() { System.out.println("Windows 风格输入框"); }
}
public class WindowsDialog implements Dialog {
// 实现 Dialog 接口,弹出 Windows 风格的对话框
public void show(String msg) { System.out.println("Windows 弹窗: " + msg); }
}
// ───────────────────────────────────────────
// 第二层:Mac 产品族
// 同样三个组件,但换成了 Mac 的外观和交互风格
// ───────────────────────────────────────────
public class MacButton implements Button {
public void render() { System.out.println("Mac 风格按钮"); }
}
public class MacInput implements Input {
public void render() { System.out.println("Mac 风格输入框"); }
}
public class MacDialog implements Dialog {
public void show(String msg) { System.out.println("Mac 弹窗: " + msg); }
}
// ───────────────────────────────────────────
// 第三层:抽象工厂接口
// 规定一个工厂必须能生产哪些组件(一整套)
// 注意:返回类型都是接口,不绑定任何具体平台
// ───────────────────────────────────────────
public interface UIFactory {
Button createButton(); // 造一个按钮
Input createInput(); // 造一个输入框
Dialog createDialog(); // 造一个弹窗
}
// ───────────────────────────────────────────
// 第四层:具体工厂
// 每个工厂只负责生产自己平台的那套组件
// ───────────────────────────────────────────
// Windows 工厂:生产的全是 Windows 产品族
public class WindowsUIFactory implements UIFactory {
public Button createButton() { return new WindowsButton(); }
public Input createInput() { return new WindowsInput(); }
public Dialog createDialog() { return new WindowsDialog(); }
}
// Mac 工厂:生产的全是 Mac 产品族
public class MacUIFactory implements UIFactory {
public Button createButton() { return new MacButton(); }
public Input createInput() { return new MacInput(); }
public Dialog createDialog() { return new MacDialog(); }
}
// ───────────────────────────────────────────
// 客户端使用
// 关键:只需要换一行工厂,整套 UI 风格全部切换
// 客户端代码完全不需要改动
// ───────────────────────────────────────────
// 根据运行环境决定用哪个工厂(这是唯一需要判断平台的地方)
UIFactory factory = isMac ? new MacUIFactory() : new WindowsUIFactory();
// 后续代码完全不感知平台,只和接口打交道
Button btn = factory.createButton();
Input input = factory.createInput();
btn.render(); // 风格统一,绝不会出现 Mac 按钮 + Windows 输入框混搭
python实现
from abc import ABC, abstractmethod
# ───────────────────────────────────────────
# 第一层:产品抽象基类
# 用 ABC + @abstractmethod 强制子类必须实现 render()
# 相当于 Java 里的接口
# ───────────────────────────────────────────
class Button(ABC):
@abstractmethod
def render(self): pass # 子类必须实现,否则实例化时报错
class Input(ABC):
@abstractmethod
def render(self): pass
# ───────────────────────────────────────────
# 第二层:Windows 产品族
# 两个类都继承自对应的抽象基类,风格统一
# ───────────────────────────────────────────
class WindowsButton(Button):
def render(self):
return "[Windows Button]" # 返回字符串,模拟渲染 Windows 风格按钮
class WindowsInput(Input):
def render(self):
return "[Windows Input]" # 渲染 Windows 风格输入框
# ───────────────────────────────────────────
# 第二层:Mac 产品族
# 同样继承抽象基类,render() 输出 Mac 风格
# ───────────────────────────────────────────
class MacButton(Button):
def render(self):
return "[Mac Button]"
class MacInput(Input):
def render(self):
return "[Mac Input]"
# ───────────────────────────────────────────
# 第三层:抽象工厂
# 规定一个工厂必须能造 Button 和 Input
# 返回类型标注为抽象基类,不绑定具体平台
# ───────────────────────────────────────────
class UIFactory(ABC):
@abstractmethod
def create_button(self) -> Button: pass # 造按钮
@abstractmethod
def create_input(self) -> Input: pass # 造输入框
# ───────────────────────────────────────────
# 第四层:具体工厂
# Windows 工厂只生产 Windows 产品,Mac 工厂只生产 Mac 产品
# ───────────────────────────────────────────
class WindowsUIFactory(UIFactory):
def create_button(self): return WindowsButton() # 返回 Windows 风格按钮
def create_input(self): return WindowsInput() # 返回 Windows 风格输入框
class MacUIFactory(UIFactory):
def create_button(self): return MacButton() # 返回 Mac 风格按钮
def create_input(self): return MacInput() # 返回 Mac 风格输入框
# ───────────────────────────────────────────
# 客户端代码
# 只依赖 UIFactory 抽象,不依赖任何具体类
# 传入不同工厂,输出不同风格,代码本身一行不用改
# ───────────────────────────────────────────
def build_ui(factory: UIFactory):
btn = factory.create_button() # 拿到按钮(不知道也不关心是哪个平台的)
inp = factory.create_input() # 拿到输入框
print(btn.render(), inp.render()) # 统一调用,风格由工厂保证一致
build_ui(MacUIFactory()) # 输出:[Mac Button] [Mac Input]
build_ui(WindowsUIFactory()) # 输出:[Windows Button] [Windows Input]
四、建造者模式(Builder)
一句话
分步骤构建一个复杂对象,同样的构建过程可以创建不同的表示。
生活中的例子
- 点汉堡套餐:你可以自选面包类型、肉饼种类、是否加芝士、是否加生菜、要什么酱料、配什么饮料。一步步选,最后组装成一个完整的套餐。
- 装修房子:地板选什么材质、墙面刷什么颜色、厨房用什么台面、卫生间装什么花洒……一项项确定,最后交付一个完整的房子。
- 写简历:先填基本信息,再填教育经历,再填工作经验,再填技能……不是一次性传 20 个参数,而是分步骤填充。
为什么需要它
当一个对象有十几个参数,构造器会变成噩梦:
// 看到这种代码你能分清哪个参数是什么吗?
new House(4, 2, true, false, true, "木质", null, 3, true, "现代");
这段代码有三个真实的痛点:
看不懂:7 个参数,没有名字,只有顺序,读代码要来回对照类定义
传错了不报错:true 和 false 顺序写反了,编译器不会提示,运行时悄悄出 bug
可选参数很麻烦:有些房子没泳池,你得要么传 false,要么写一堆重载构造器
参数一多,顺序一乱就出 bug。有些参数是可选的,有些有默认值,用构造器重载的话要写几十个重载方法。
Builder 怎么解决问题的:
// 每个参数都有名字,顺序随意,不需要的直接不写
House h = new House.Builder()
.rooms(6)
.floors(3)
.garage(true) // 明确写"车库=true",不会传错位置
.style("欧式") // 没有泳池?直接不写 .pool(),用默认值
.build();
链式调用为什么能一直 . 下去?
每个方法都 return this,意思是"执行完这步,把 Builder 自己还给你,你继续操作":
// 拆开看链式调用的执行过程:
Builder b = new House.Builder(); // 创建 Builder
b = b.rooms(6); // 设置房间,返回同一个 Builder
b = b.floors(3); // 设置楼层,返回同一个 Builder
b = b.style("欧式"); // 设置风格,返回同一个 Builder
House h = b.build(); // 最后组装,返回 House
// 链式写法只是把上面四步合并成一行,本质完全一样
build() 为什么重要
build() 不只是"返回对象",它是统一校验的最后关口:
public House build() {
// 所有校验集中在这里,不用在每个 setter 里单独写
if (house.floors < 1) throw new IllegalArgumentException("至少一层");
if (house.rooms < 1) throw new IllegalArgumentException("至少一间房");
if (house.style == null) throw new IllegalArgumentException("风格不能为空");
return house; // 能走到这里,对象一定是合法的
}
好处是:build() 返回的对象一定是完整合法的,不可能拿到一个"建了一半"的 House。
私有构造器的作用
private House() {}
这一行让 House 无法被外部 new,强制所有人必须走 Builder。
如果没有这行,有人直接 new House() 绕过 Builder,拿到的是所有字段都是默认值的空对象,可能引发各种问题。私有构造器堵死了这条路。
Builder 和单例的对比
两者都用了私有构造器,但目的完全不同:
| 单例 | Builder | |
|---|---|---|
| 私有构造器的目的 | 防止创建多个实例 | 强制走 Builder 流程 |
| 最终能创建几个对象 | 只有一个 | 想创建多少创建多少 |
| 关注点 | 数量控制 | 构建过程控制 |
什么时候用 Builder
满足以下任一条件就可以考虑:
- 构造参数超过 4 个
- 有多个可选参数(不是每次都要传)
- 参数之间有校验依赖关系(比如"有泳池必须有花园")
- 想让构建过程的代码具有可读性
参数少于 4 个、全部必填、没有复杂校验的话,直接普通构造器就够了,不必强套 Builder。
结构图

代码实现
java实现
// ───────────────────────────────────────────
// 目标对象:House
// 字段全部私有,外部无法直接赋值
// 也没有带参数的构造器,唯一的创建入口是内部的 Builder
// ───────────────────────────────────────────
public class House {
private int rooms; // 房间数
private int floors; // 楼层数
private boolean garage; // 是否有车库
private boolean pool; // 是否有泳池
private boolean garden; // 是否有花园
private String roofType; // 屋顶类型
private String style; // 装修风格
// 构造器私有:House 不能被外部 new,必须通过 Builder 创建
// 这样可以保证对象只能通过 build() 拿到,便于统一校验
private House() {}
// ───────────────────────────────────────────
// 静态内部类:Builder
// 负责一步步收集参数,最后组装出 House 对象
// ───────────────────────────────────────────
public static class Builder {
// Builder 内部持有一个待组装的 House 实例
private House house = new House();
// 每个 setter 方法做两件事:
// 1. 给 house 对应字段赋值
// 2. return this —— 返回 Builder 自身,这是链式调用的关键
public Builder rooms(int n) { house.rooms = n; return this; }
public Builder floors(int n) { house.floors = n; return this; }
public Builder garage(boolean b) { house.garage = b; return this; }
public Builder pool(boolean b) { house.pool = b; return this; }
public Builder garden(boolean b) { house.garden = b; return this; }
public Builder roofType(String s) { house.roofType = s; return this; }
public Builder style(String s) { house.style = s; return this; }
// build():链式调用的终点,返回最终组装好的 House 对象
// 在这里统一做参数校验,比在每个 setter 里单独校验更集中
public House build() {
if (house.floors < 1) throw new IllegalArgumentException("至少一层");
return house; // 校验通过后,把组装好的 House 交出去
}
}
}
// ───────────────────────────────────────────
// 客户端使用:链式调用
// 每个方法名就是参数名,一眼看出每个值的含义
// 对比 new House(6, 3, true, true, true, "坡顶", "欧式") —— 根本看不懂
// ───────────────────────────────────────────
// 别墅:6间房,3层,有车库/泳池/花园,欧式坡顶
House villa = new House.Builder()
.rooms(6)
.floors(3)
.garage(true)
.pool(true)
.garden(true)
.roofType("坡顶")
.style("欧式")
.build(); // 最后调用 build() 触发校验并拿到对象
// 公寓:2间房,1层,没有车库和泳池,现代简约风
// 不需要的参数直接不写,不影响其他字段
House apartment = new House.Builder()
.rooms(2)
.floors(1)
.garage(false)
.pool(false)
.style("现代简约")
.build();
python实现
# ───────────────────────────────────────────
# 目标对象:House
# 只负责存数据,所有字段初始化为默认值
# 不做任何校验,校验逻辑集中在 HouseBuilder.build() 里
# ───────────────────────────────────────────
class House:
def __init__(self):
self.rooms = 0 # 房间数,默认 0
self.floors = 0 # 楼层数,默认 0
self.garage = False # 是否有车库,默认没有
self.pool = False # 是否有泳池,默认没有
self.garden = False # 是否有花园,默认没有
self.style = "" # 装修风格,默认空
def __repr__(self):
# 控制 print(house) 时的输出格式,方便调试查看
return f"House(rooms={self.rooms}, floors={self.floors}, style='{self.style}')"
# ───────────────────────────────────────────
# 建造者:HouseBuilder
# 持有一个 House 实例,通过链式调用逐步填充字段
# ───────────────────────────────────────────
class HouseBuilder:
def __init__(self):
self._house = House() # 内部持有待组装的 House,下划线表示不对外暴露
# 每个方法:赋值 → return self
# return self 是链式调用的核心:返回 Builder 本身
# 这样才能一直 .rooms().floors().style() 连续调用下去
def rooms(self, n):
self._house.rooms = n
return self # 返回自身,支持链式调用
def floors(self, n):
self._house.floors = n
return self
def garage(self, b=True): # 默认值 True:直接写 .garage() 就代表"要车库"
self._house.garage = b
return self
def pool(self, b=True): # 同上,.pool() 等价于 .pool(True)
self._house.pool = b
return self
def garden(self, b=True):
self._house.garden = b
return self
def style(self, s):
self._house.style = s
return self
# build():链式调用的终点
# 统一做校验,校验通过后把组装好的 House 对象返回给调用方
def build(self):
if self._house.floors < 1:
raise ValueError("至少一层") # 校验失败直接抛异常,不返回非法对象
return self._house # 校验通过,交出最终对象
# ───────────────────────────────────────────
# 客户端使用
# 外层括号只是为了让链式调用可以换行,不影响逻辑
# ───────────────────────────────────────────
villa = (HouseBuilder()
.rooms(6)
.floors(3)
.garage() # 等价于 .garage(True),语义更简洁
.pool()
.garden()
.style("欧式")
.build()) # 触发校验并拿到 House 对象
print(villa) # House(rooms=6, floors=3, style='欧式')
Python 中更 Pythonic 的做法是用
dataclass+ 默认参数,但当参数间有复杂校验逻辑或构建步骤有依赖关系时,Builder 仍然有价值。
Java 的 Lombok
@Builder注解可以自动生成这套代码。
五、原型模式(Prototype)
一句话
用一个已有对象作为模板,复制出新对象,而不是从零开始构建。
生活中的例子
- 复印文件:第一份文件排版好了,后面再要 100 份直接复印,不用每份都重新排版。
- PPT 模板:打开一个现成的模板,在上面改内容就行,不用每次从空白 PPT 开始。
- 游戏里刷怪:设计好一个"狼"的原型(属性、技能、AI),然后在地图各处"克隆"出 100 只,每只只改一下坐标和等级。
- Excel 复制行:选中一行数据,复制粘贴,改几个字段——比从空行开始填快得多。
为什么需要它
有些对象创建成本很高:
- 需要查数据库加载大量数据
- 需要经过复杂计算或远程调用
- 初始化过程涉及读取配置文件、建立网络连接
如果你需要 1000 个这样的对象,每次都走一遍这个流程,代价是不可接受的。每次都从头构建太浪费,不如克隆一个已有的,再微调。
// 每次都从数据库加载,耗时 200ms × 1000 次 = 200 秒
for (int i = 0; i < 1000; i++) {
GameUnit soldier = loadFromDB("elite_soldier"); // 慢
}
// 原型模式:只加载一次,后续全部克隆,0.1ms × 1000 次 = 0.1 秒
GameUnit template = loadFromDB("elite_soldier"); // 只慢这一次
for (int i = 0; i < 1000; i++) {
GameUnit soldier = template.clone(); // 快
}
注意:深拷贝 vs 浅拷贝,这是原型模式最容易踩的坑
克隆不等于完全独立,取决于你用的是哪种拷贝:
原型对象
├── hp = 100 ← 基本类型,两种拷贝都会独立复制
├── skills = [冲锋, 格挡] ← 引用类型,两种拷贝行为不同
└── position = (0,0) ← 引用类型,同上
浅拷贝:只复制一层。基本类型值独立,但引用类型只复制了"地址",克隆体和原型共享同一个对象。
// 浅拷贝的危险
GameUnit copy = (GameUnit) super.clone(); // 浅拷贝
copy.skills.add("狂暴"); // 改的是同一个列表!
// 原型的 skills 也变了 —— 这是 bug
深拷贝:递归复制所有嵌套对象,克隆体和原型完全独立,互不影响。
// 深拷贝,手动处理引用类型字段
GameUnit copy = (GameUnit) super.clone();
copy.skills = new ArrayList<>(this.skills); // 新建独立列表
copy.position = new Position(0, 0); // 新建独立位置
// 现在改 copy.skills,原型不受影响
原则很简单:字段里有可变对象(List、Map、自定义类),就必须深拷贝。
| 类型 | 含义 | 风险 | 适用场景 |
|---|---|---|---|
| 浅拷贝 | 只复制一层,引用对象共享 | 改副本,原件跟着变 | 对象内部只有基本类型 |
| 深拷贝 | 递归复制所有嵌套对象 | 安全但慢一点 | 对象内部有集合、可变对象 |
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original) # 浅拷贝
deep = copy.deepcopy(original) # 深拷贝
original[0].append(99)
print(shallow) # [[1, 2, 99], [3, 4]] ← 被影响了!
print(deep) # [[1, 2], [3, 4]] ← 独立的,没被影响
结构图

代码实现
java代码
// ───────────────────────────────────────────
// 游戏单位类,实现 Cloneable 接口
// 实现 Cloneable 是 Java 克隆的前提,否则调用 clone() 会抛异常
// ───────────────────────────────────────────
public class GameUnit implements Cloneable {
private String type; // 单位类型,如"精英士兵"
private int hp; // 生命值
private int attack; // 攻击力
private List<String> skills; // 技能列表(可变对象,克隆时需要特殊处理)
private Position position; // 位置坐标(可变对象,克隆时需要特殊处理)
@Override
public GameUnit clone() {
try {
// super.clone() 是 Java 内置的浅拷贝
// 基本类型字段(int、boolean 等)会被正确复制
// 但引用类型字段(List、Position 等)只复制了引用,
// 克隆体和原型会共享同一个对象 —— 这是浅拷贝的危险所在
GameUnit copy = (GameUnit) super.clone();
// 手动深拷贝 skills:用原列表内容新建一个独立列表
// 如果不这样做,copy.skills 和 this.skills 指向同一个列表
// 克隆体添加技能会影响到原型,绝对不能允许
copy.skills = new ArrayList<>(this.skills);
// 手动深拷贝 position:新建一个位置对象,初始坐标设为 (0,0)
// 每个克隆体有自己独立的位置,后续再单独设置到地图上的具体坐标
copy.position = new Position(0, 0);
return copy; // 返回完整独立的克隆体
} catch (CloneNotSupportedException e) {
// 理论上不会走到这里,因为已经实现了 Cloneable
// 但 Java 语法要求必须处理这个受检异常
throw new RuntimeException(e);
}
}
}
// ───────────────────────────────────────────
// 客户端:以"士兵模板"为原型,批量克隆 1000 个士兵
// ───────────────────────────────────────────
// 第一步:创建原型模板(这步才是真正耗时的操作)
// loadExpensiveUnitFromDB 需要:查数据库 + 加载贴图 + 计算属性,耗时约 200ms
GameUnit soldierTemplate = loadExpensiveUnitFromDB("elite_soldier");
// 第二步:批量克隆(克隆只是内存复制,每次约 0.1ms)
for (int i = 0; i < 1000; i++) {
// clone() 直接从内存复制,不走数据库,速度极快
GameUnit soldier = soldierTemplate.clone();
// 每个克隆体设置不同的出生位置,其余属性(hp/attack/skills)完全继承原型
soldier.setPosition(randomPosition());
battlefield.add(soldier);
}
// 性能对比:
// 不用原型:1000 × 200ms = 200,000ms(200 秒)
// 用原型: 200ms(一次加载)+ 1000 × 0.1ms = 300ms
// 省了将近 200 秒!
python代码
import copy # Python 内置库,提供浅拷贝(copy)和深拷贝(deepcopy)
# ───────────────────────────────────────────
# 游戏单位类
# ───────────────────────────────────────────
class GameUnit:
def __init__(self, unit_type, hp, attack, skills):
self.unit_type = unit_type # 单位类型
self.hp = hp # 生命值
self.attack = attack # 攻击力
self.skills = skills # 技能列表(列表是可变对象,深拷贝时会独立复制)
self.position = (0, 0) # 初始位置,元组是不可变对象,浅拷贝也安全
def clone(self):
"""
深拷贝:递归复制所有嵌套对象,确保克隆体与原型完全独立
用 copy.deepcopy 而不是 copy.copy(浅拷贝),
因为 skills 是列表,浅拷贝只复制引用,改克隆体会影响原型
"""
return copy.deepcopy(self)
def __repr__(self):
# 控制 print 输出格式,方便调试
return f"{self.unit_type}(hp={self.hp}, pos={self.position})"
# ───────────────────────────────────────────
# 第一步:创建原型模板
# 假设这步需要查数据库、加载图片资源,非常耗时
# 只做一次,后续都从这个模板克隆
# ───────────────────────────────────────────
soldier_template = GameUnit(
unit_type="精英士兵",
hp=100,
attack=25,
skills=["冲锋", "格挡", "反击"] # 这个列表属于原型,绝不能被克隆体修改
)
# ───────────────────────────────────────────
# 第二步:批量克隆 1000 个士兵
# 每次 clone() 只是内存操作,极快
# ───────────────────────────────────────────
army = []
for i in range(1000):
soldier = soldier_template.clone() # 深拷贝,产生完全独立的新对象
soldier.position = (i * 10, 0) # 每个士兵放到不同位置,其余属性继承原型
army.append(soldier)
# ───────────────────────────────────────────
# 验证深拷贝的效果:改克隆体不影响原型
# 如果用的是浅拷贝,下面这行会同时修改 soldier_template.skills
# ───────────────────────────────────────────
army[0].skills.append("狂暴") # 给第一个克隆体新增技能
print("原型技能:", soldier_template.skills)
# 输出:['冲锋', '格挡', '反击'] ← 没变,说明深拷贝成功,两者完全独立
print("克隆体技能:", army[0].skills)
# 输出:['冲锋', '格挡', '反击', '狂暴'] ← 只有克隆体变了
总结:五种创建型模式对比
| 模式 | 核心意图 | 现实比喻 | 典型场景 |
|---|---|---|---|
| 单例 | 全局只要一个实例 | 一个国家一个主席 | 配置、连接池、日志 |
| 工厂方法 | 让子类决定创建哪种对象 | 披萨连锁店各分店 | 多种产品,按类型切换 |
| 抽象工厂 | 创建一族相关对象 | 宜家风格全家桶 | 跨平台 UI、多数据源 |
| 建造者 | 分步构建复杂对象 | 装修房子一项项选 | 参数多的对象、链式构建 |
| 原型 | 克隆现有对象 | 复印文件 | 大量相似对象、创建成本高 |
心法
- 不要为了用模式而用模式。 如果
new一行代码就能搞定,别硬套工厂。就像两个人吃饭不用去订宴会厅。 - 看场景选模式。 模式是工具箱里的扳手,不是锤子——别看什么都像钉子。
- 组合使用很常见。 比如 Builder 内部可能用工厂方法创建子组件,单例工厂也很常见。Spring 框架里 Bean 默认就是单例 + 工厂的组合。

创建型模式关心的核心问题只有一个:对象怎么来的? 把"怎么创建对象"这件事封装起来,让使用者不需要知道细节,拿来就用。
浙公网安备 33010602011771号