Golang学习笔记_31——原型模式
Golang学习笔记_28——工厂方法模式
Golang学习笔记_29——抽象工厂模式
Golang学习笔记_30——建造者模式
文章目录
- 一、原型模式核心概念
- 1. 定义
- 2. 解决的问题
- 3. 核心角色
- 4. 类图
- 二、原型模式的特点
- 三、适用场景
- 1. 对象创建成本高
- 2. 需要动态配置对象
- 3. 避免重复初始化
- 4. 需要隔离对象构造细节
- 四、与其他创建型模式的对比
- 五、Go 语言代码示例
- 六、原型模式的高级用法
- 1. 原型注册表
- 2. 动态配置对象
- 3. 组合模式结合
- 七、总结
一、原型模式核心概念
1. 定义
原型模式是一种 创建型设计模式,通过 复制现有对象(原型) 来创建新对象,而不是通过构造函数实例化。它适用于需要高效创建对象或动态配置对象的场景。
2. 解决的问题
- 对象创建成本高:例如,对象初始化需要复杂计算、数据库查询或网络请求。
- 动态配置对象:运行时需要基于现有对象的状态生成新对象。
- 避免重复初始化:直接复制已有对象,跳过初始化步骤。
3. 核心角色
- Prototype(原型接口):声明克隆自身的方法(如
Clone()
)。 - ConcretePrototype(具体原型):实现
Clone()
方法的具体类。 - Client(客户端):通过调用
Clone()
方法生成新对象。
4. 类图
@startuml
' 定义接口和类
interface Prototype {
+ Clone(): Prototype
}
class Resume {
- Name: string
- Age: int
- Skills: []string
- Contact: map[string]string
+ Clone(): Prototype
+ Show(): void
}
' 实现关系
Resume .up.|> Prototype : implements
' 添加注释说明浅拷贝问题
note left of Resume::Clone
**浅拷贝**:Skills切片和Contact映射
与原对象共享底层数据!
end note
@enduml
二、原型模式的特点
优点
-
性能优化
直接复制对象,避免重复执行耗时的初始化操作(如数据库连接)。 -
动态配置对象
可以基于运行时状态克隆对象,并修改其属性。 -
避免构造约束
绕过构造函数中的限制(例如私有构造函数或复杂参数)。 -
简化对象创建
客户端无需知道对象的具体类型,只需调用Clone()
。
缺点
-
深拷贝复杂性
如果对象包含引用类型(如指针、切片、Map),需要手动实现深拷贝。 -
需要实现克隆方法
每个需要复制的类必须实现Clone()
方法,增加代码量。 -
可能破坏封装性
某些语言中,克隆私有字段可能违反面向对象设计原则。
三、适用场景
1. 对象创建成本高
- 示例:数据库连接对象、缓存对象、复杂配置对象。
- 解决:首次创建后,后续通过克隆复用。
2. 需要动态配置对象
- 示例:游戏中的敌人角色,基于现有角色生成变种(如修改颜色、攻击力)。
- 解决:克隆基础对象,再修改特定属性。
3. 避免重复初始化
- 示例:从模板生成文档(如合同、报告),模板初始化复杂。
- 解决:克隆模板对象后填充数据。
4. 需要隔离对象构造细节
- 示例:系统需要创建对象但无法直接访问其构造函数。
- 解决:通过原型接口克隆对象。
四、与其他创建型模式的对比
模式 | 核心目标 | 关键区别 |
---|---|---|
工厂方法 | 通过子类创建对象 | 通过继承实现,依赖具体类。 |
抽象工厂 | 创建产品族 | 关注对象家族,而非复制已有对象。 |
建造者 | 分步骤构造复杂对象 | 强调构造过程的控制,而非复制。 |
原型 | 通过克隆生成对象 | 直接复制现有对象,绕过构造函数。 |
五、Go 语言代码示例
场景描述
实现一个 简历生成系统,用户可以通过模板简历快速克隆并修改生成个性化简历。
代码实现
- 浅拷贝示例
package prototype_demo
import (
"encoding/json"
"fmt"
)
// 原型接口
type Prototype interface {
Clone() Prototype
}
// 具体原型:简历
type Resume struct {
Name string
Age int
Skills []string
Contact map[string]string
}
func (r *Resume) Clone() Prototype {
// 浅拷贝
return &Resume{
Name: r.Name,
Age: r.Age,
Skills: r.Skills, // 浅拷贝切片 (共享底层数组)
Contact: r.Contact, // 浅拷贝Map (共享底层哈希表)
}
}
func (r *Resume) Display() {
fmt.Printf("简历信息:\n"+
" 姓名: %s\n"+
" 年龄: %d\n"+
" 技能: %v\n"+
" 联系方式: %v\n",
r.Name, r.Age, r.Skills, r.Contact)
}
// 深拷贝
func (r *Resume) DeepClone() Prototype {
// 深拷贝
data, _ := json.Marshal(r)
var newResume Resume
json.Unmarshal(data, &newResume)
return &newResume
}
// 第二种深拷贝的方法
func (r *Resume) DeepClone2() Prototype {
// 手动创建新对象并深度复制所有引用类型字段
newResume := &Resume{
Name: r.Name,
Age: r.Age,
}
// 深拷贝切片
newResume.Skills = make([]string, len(r.Skills))
copy(newResume.Skills, r.Skills)
// 深拷贝Map
newResume.Contact = make(map[string]string)
for k, v := range r.Contact {
newResume.Contact[k] = v
}
return newResume
}
func test() {
// 原始简历模板
templateResume := &Resume{
Name: "张三",
Age: 25,
Skills: []string{"Go", "Java"},
Contact: map[string]string{
"Email": "zhangsan@example.com",
"Phone": "123456789",
},
}
// 克隆简历并修改
cloneResume := templateResume.Clone().(*Resume)
cloneResume.Name = "李四"
cloneResume.Skills = append(cloneResume.Skills, "Python") // 修改切片(会影响原对象!)
cloneResume.Contact["Phone"] = "987654321"
// 显示原始简历和克隆简历
templateResume.Display()
cloneResume.Display()
}
func test2() {
// 原始简历模板
templateResume := &Resume{
Name: "张三",
Age: 25,
Skills: []string{"Go", "Java"},
Contact: map[string]string{
"Email": "zhangsan@example.com",
"Phone": "123456789",
},
}
deepCloneResume := templateResume.DeepClone().(*Resume)
deepCloneResume.Name = "李四"
deepCloneResume.Skills = append(deepCloneResume.Skills, "Python") // 修改切片(不会影响原对象!)
deepCloneResume.Contact["Phone"] = "987654321"
deepCloneResume.Contact["Email"] = "lisi@example.com"
templateResume.Display()
deepCloneResume.Display()
}
func test3() {
templateResume := &Resume{
Name: "张三",
Age: 25,
Skills: []string{"Go", "Java"},
Contact: map[string]string{
"Email": "zhangsan@example.com",
"Phone": "123456789",
},
}
deepCloneResume := templateResume.DeepClone2().(*Resume)
deepCloneResume.Name = "李四"
deepCloneResume.Skills = append(deepCloneResume.Skills, "Python") // 修改切片(不会影响原对象!)
deepCloneResume.Contact["Phone"] = "987654321"
deepCloneResume.Contact["Email"] = "lisi@example.com"
templateResume.Display()
deepCloneResume.Display()
}
输出结果
=== RUN Test_test
-----------------------浅拷贝----------------------------
简历信息:
姓名: 张三
年龄: 25
技能: [Go Java]
联系方式: map[Email:zhangsan@example.com Phone:987654321]
简历信息:
姓名: 李四
年龄: 25
技能: [Go Java Python]
联系方式: map[Email:zhangsan@example.com Phone:987654321]
-----------------------深拷贝1----------------------------
简历信息:
姓名: 张三
年龄: 25
技能: [Go Java]
联系方式: map[Email:zhangsan@example.com Phone:123456789]
简历信息:
姓名: 李四
年龄: 25
技能: [Go Java Python]
联系方式: map[Email:lisi@example.com Phone:987654321]
-----------------------深拷贝2----------------------------
简历信息:
姓名: 张三
年龄: 25
技能: [Go Java]
联系方式: map[Email:zhangsan@example.com Phone:123456789]
简历信息:
姓名: 李四
年龄: 25
技能: [Go Java Python]
联系方式: map[Email:lisi@example.com Phone:987654321]
--- PASS: Test_test (0.00s)
PASS
六、原型模式的高级用法
1. 原型注册表
维护一个全局注册表,存储可复用的原型对象:
type PrototypeRegistry struct {
prototypes map[string]Prototype
}
func (r *PrototypeRegistry) Get(key string) Prototype {
return r.prototypes[key].Clone()
}
func (r *PrototypeRegistry) Set(key string, p Prototype) {
r.prototypes[key] = p
}
2. 动态配置对象
克隆后动态修改对象属性:
// 克隆并修改技能
newResume := templateResume.Clone().(*Resume)
newResume.Skills = []string{"Go", "Rust"}
3. 组合模式结合
复杂对象的组件也可以通过原型模式复制:
type Project struct {
Name string
Details *Resume // 组合 Resume 对象
}
func (p *Project) Clone() Prototype {
return &Project{
Name: p.Name,
Details: p.Details.Clone().(*Resume),
}
}
七、总结
原型模式通过 复制现有对象 的方式创建新对象,特别适合以下场景:
- 对象创建成本高:避免重复初始化。
- 需要动态配置对象:基于运行时状态生成变种。
- 隔离构造细节:绕过复杂构造函数。
在 Go 中实现时需注意 深拷贝问题,可通过序列化或手动复制引用类型字段解决。如果需要更复杂的深拷贝逻辑(如循环引用处理),可以结合其他技术(如反射、代码生成工具)实现。