原型模式:复制比新建更快
你有没有用过"复制粘贴"?选中一个文件,Ctrl+C → Ctrl+V,瞬间就有了一个副本。原型模式就是程序世界的"复制粘贴"—— 通过克隆现有对象来创建新对象,而不是从零开始构造。
原型模式 允许你复制已有对象,而无需让代码依赖它们所属的类。
为什么需要原型模式?
假设你需要复制一个复杂对象:
// ❌ 糟糕的写法:手动复制每个字段
func copyFile(original *File) *File {
copy := &File{
Name: original.Name,
Size: original.Size,
Content: original.Content,
// 私有字段怎么办?
// 嵌套对象怎么办?
// 如果不知道具体类型怎么办?
}
return copy
}
这样写的问题:
- 私有字段无法访问 :外部代码看不到私有成员
- 嵌套对象难处理 :深拷贝还是浅拷贝?
- 依赖具体类 :必须知道对象的确切类型
原型模式的解法: 让对象自己负责克隆自己,因为它最清楚自己有哪些字段。
模式结构
| 角色 | 职责 | 类比 |
|---|---|---|
| Prototype(原型接口) | 声明克隆方法 | "可复制"能力 |
| ConcretePrototype(具体原型) | 实现克隆逻辑 | 文件、文件夹 |
| Client(客户端) | 调用克隆方法获取副本 | 使用复制功能的人 |
动手实现:文件系统复制
用文件系统的复制功能来演示原型模式。文件和文件夹都可以被克隆。
第一步:定义原型接口
第二步:实现具体原型
package main
import "fmt"
// Folder 文件夹节点
type Folder struct {
Name string
Children []Inode
}
func (f *Folder) Print(indent string) {
fmt.Println(indent + "📁 " + f.Name)
for _, child := range f.Children {
child.Print(indent + " ")
}
}
// Clone 深度克隆文件夹及其所有子节点
func (f *Folder) Clone() Inode {
cloneFolder := &Folder{Name: f.Name + "_copy"}
// 递归克隆所有子节点
for _, child := range f.Children {
cloneFolder.Children = append(cloneFolder.Children, child.Clone())
}
return cloneFolder
}
第三步:使用原型模式
package main
import "fmt"
func main() {
// 创建原始文件结构
file1 := &File{Name: "报告.docx"}
file2 := &File{Name: "数据.xlsx"}
file3 := &File{Name: "图片.png"}
folder1 := &Folder{
Name: "文档",
Children: []Inode{file1},
}
folder2 := &Folder{
Name: "项目",
Children: []Inode{folder1, file2, file3},
}
fmt.Println("=== 原始文件夹结构 ===")
folder2.Print("")
// 克隆整个文件夹
clonedFolder := folder2.Clone()
fmt.Println("\n=== 克隆后的文件夹结构 ===")
clonedFolder.Print("")
}
深拷贝 vs 浅拷贝
这是原型模式中最重要的概念:
| 类型 | 说明 | 后果 |
|---|---|---|
| 浅拷贝 | 只复制对象本身,嵌套对象共享引用 | 修改副本会影响原对象 |
| 深拷贝 | 递归复制所有嵌套对象 | 完全独立,但开销更大 |
// 浅拷贝示例
func (f *Folder) ShallowClone() *Folder {
return &Folder{
Name: f.Name,
Children: f.Children, // 共享同一个切片!
}
}
// 深拷贝示例(推荐)
func (f *Folder) DeepClone() *Folder {
clone := &Folder{Name: f.Name}
for _, child := range f.Children {
clone.Children = append(clone.Children, child.Clone())
}
return clone
}
什么时候该用原型模式?
| 场景 | 说明 |
|---|---|
| 对象创建成本高 | 如需要读取配置、连接数据库初始化 |
| 需要保存对象状态 | 如游戏存档、文档历史版本 |
| 不想依赖具体类 | 只知道接口,不知道具体类型 |
| 需要复制复杂对象 | 如包含嵌套结构的组合对象 |
常见应用 :
- 图形编辑器 :复制图形对象
- 游戏开发 :克隆敌人、道具
- 缓存系统 :缓存对象模板,需要时克隆
- 配置对象 :复制默认配置后修改
优缺点分析
| ✅ 优点 | ❌ 缺点 |
|---|---|
| 不依赖具体类 :通过接口复制对象 | 循环引用麻烦 :A 引用 B,B 引用 A |
| 避免重复初始化 :克隆比新建快 | 深拷贝复杂 :嵌套层级深时实现复杂 |
| 动态配置对象 :克隆后再修改 |
与其他模式的关系
| 模式组合 | 说明 |
|---|---|
| 原型 + 命令 | 保存命令执行前的对象状态 |
| 原型 vs 备忘录 | 备忘录保存状态,原型复制整个对象 |
| 原型 + 工厂 | 工厂内部可以用原型来创建对象 |
一句话总结 :原型模式就像细胞分裂——从一个细胞复制出完全相同的新细胞,比重新长一个细胞快多了。
