文章目录
-
- 一、原型模式概述
-
- [1. 什么是原型模式?](#1. 什么是原型模式?)
- [1.2 现实生活中的比喻](#1.2 现实生活中的比喻)
- [1.3 原型模式的优缺点](#1.3 原型模式的优缺点)
- [1.4 适用场景](#1.4 适用场景)
- [1.5 原型模式的UML图与核心角色](#1.5 原型模式的UML图与核心角色)
- [1.6 Go语言实现:浅拷贝与深拷贝](#1.6 Go语言实现:浅拷贝与深拷贝)
- 二、Go语言实现:一个完整的例子
一、原型模式概述
1. 什么是原型模式?
原型模式是一种创建型设计模式 ,它允许你通过复制(或克隆)一个现有的实例来创建新的实例,而不是通过 new 关键字和使用构造函数。
核心思想:创建对象的成本可能很高(例如,需要从数据库读取大量数据进行初始化),这时我们可以先创建一个原型对象,然后通过克隆这个原型来快速创建新的对象。
1.2 现实生活中的比喻
- 细胞分裂:一个细胞通过自我复制(克隆)产生一个完全相同的新细胞。新细胞继承了原细胞的所有特性,然后可以独立发展。
- 打印文件:你有一份复杂的电子文档(原型),你需要多份副本。你不会重新手动创建每一份,而是使用打印机的"复印"功能(克隆)来快速得到一模一样的副本。
1.3 原型模式的优缺点
优点:
- 提高性能:当创建对象的成本很高时,克隆比重新初始化要快得多。
- 简化对象创建:客户端代码不需要关心对象创建的复杂细节,只需调用一个克隆方法即可。
- 增加灵活性:可以在运行时动态地添加或删除要克隆的对象。
- 减少子类数量:如果创建对象需要复杂的工厂方法,使用原型模式可以避免创建庞大的工厂类层次结构。
缺点:
- 实现克隆可能很复杂:特别是当对象包含循环引用或对不支持深拷贝的资源(如文件句柄、网络连接)的引用时,实现深拷贝会非常困难。
- 需要为每个类实现克隆方法:这增加了代码的复杂性,并且必须处理深拷贝与浅拷贝的问题。
1.4 适用场景
- 创建对象成本高昂:例如,对象需要通过昂贵的数据库查询或RPC调用来初始化。
- 需要避免与对象创建相关的工厂类层次结构。
- 对象的实例状态只有少数几种组合:可以预先创建好这些组合的原型,然后按需克隆。
- 当客户端不需要知道对象创建的具体细节时。
1.5 原型模式的UML图与核心角色
原型模式的核心角色非常简单:
-
Prototype(原型接口) :定义一个用于克隆自己的方法,通常是
Clone()。 -
ConcretePrototype(具体原型) :实现
Prototype接口,并实现具体的克隆逻辑。
UML 类图:+----------------+ +----------------------+
| Client |------>| Prototype |
+----------------+ +----------------------+
| - prototype: Prototype | | + Clone() Prototype |
| + Operation() | +----------------------+
+----------------+ ^
|
| implements
|
+----------------------+
| ConcretePrototype |
+----------------------+
| + field: type |
| + Clone() Prototype |
+----------------------+
1.6 Go语言实现:浅拷贝与深拷贝
在Go中实现原型模式,关键在于理解浅拷贝 和深拷贝的区别。
- 浅拷贝 :只复制对象本身和其包含的所有值类型 字段。对于引用类型(如切片、Map、指针、Channel),它只复制引用(地址),而不复制引用指向的数据。这意味着新对象和原型对象会共享同一份引用数据。
- 深拷贝 :不仅复制对象本身,还会递归地复制所有引用类型字段指向的数据。新对象和原型对象完全不共享任何数据,是完全独立的副本。
Go语言中,可以通过&*操作符轻松实现浅拷贝,但深拷贝通常需要手动实现,或者使用序列化/反序列化的技巧(如json.Marshal/Unmarshal)。
二、Go语言实现:一个完整的例子
假设我们正在开发一个游戏,需要创建多种具有不同配置的敌人。创建一个敌人的成本很高(需要加载模型、贴图、AI配置等),所以我们使用原型模式。
第1步:定义原型接口
go
// Prototype: 定义一个可克隆的原型接口
type Prototype interface {
Clone() Prototype
GetInfo() string
}
第2步:创建具体原型(包含浅拷贝和深拷贝的对比)
我们创建一个 Enemy 结构体,它包含一个值类型字段 Name 和一个引用类型字段 Weapons(一个切片)。
go
// ConcretePrototype: 敌人结构体
type Enemy struct {
Name string
Health int
Weapons []string // 引用类型
}
// Clone 实现浅拷贝
func (e *Enemy) Clone() Prototype {
// 浅拷贝:复制结构体,但切片 Weapons 只复制了引用(地址)
fmt.Println("--> Performing Shallow Clone...")
clone := *e // 复制结构体
return &clone
}
// GetInfo 返回敌人信息
func (e *Enemy) GetInfo() string {
return fmt.Sprintf("Enemy{Name: %s, Health: %d, Weapons: %v}", e.Name, e.Health, e.Weapons)
}
// CloneDeep 实现深拷贝
func (e *Enemy) CloneDeep() Prototype {
fmt.Println("--> Performing Deep Clone...")
// 深拷贝:需要手动复制所有引用类型字段
clone := &Enemy{
Name: e.Name,
Health: e.Health,
Weapons: make([]string, len(e.Weapons)), // 创建一个新的切片
}
// 将原切片的元素复制到新切片中
copy(clone.Weapons, e.Weapons)
return clone
}
第3步:客户端使用
go
func main() {
fmt.Println("--- Prototype Pattern in Go ---")
// 1. 创建一个原型敌人
originalEnemy := &Enemy{
Name: "Goblin Archer",
Health: 50,
Weapons: []string{"Short Bow", "Dagger"},
}
fmt.Printf("Original Enemy: %s (Weapons Address: %p)\n", originalEnemy.GetInfo(), originalEnemy.Weapons)
fmt.Println("\n--- Testing Shallow Clone ---")
// 2. 克隆一个新敌人(浅拷贝)
shallowClone := originalEnemy.Clone().(*Enemy)
fmt.Printf("Shallow Clone: %s (Weapons Address: %p)\n", shallowClone.GetInfo(), shallowClone.Weapons)
// 3. 修改克隆对象的引用类型字段
fmt.Println("\nModifying shallow clone's weapon...")
shallowClone.Weapons[0] = "Long Bow"
// 4. 检查原对象是否受影响
fmt.Printf("After modification:\n")
fmt.Printf("Original Enemy: %s\n", originalEnemy.GetInfo()) // 原对象的武器也被修改了!
fmt.Printf("Shallow Clone: %s\n", shallowClone.GetInfo())
fmt.Println("\n========================================\n")
fmt.Println("--- Testing Deep Clone ---")
// 5. 克隆一个新敌人(深拷贝)
deepClone := originalEnemy.CloneDeep().(*Enemy)
fmt.Printf("Deep Clone: %s (Weapons Address: %p)\n", deepClone.GetInfo(), deepClone.Weapons)
// 6. 修改深拷贝对象的引用类型字段
fmt.Println("\nModifying deep clone's weapon...")
deepClone.Weapons[0] = "Crossbow"
// 7. 检查原对象是否受影响
fmt.Printf("After modification:\n")
fmt.Printf("Original Enemy: %s\n", originalEnemy.GetInfo()) // 原对象不受影响
fmt.Printf("Deep Clone: %s\n", deepClone.GetInfo())
}
完整代码(可直接运行)
go
package main
import "fmt"
// ======================
// 1. 定义原型接口
// ======================
// Prototype: 定义一个可克隆的原型接口
type Prototype interface {
Clone() Prototype
GetInfo() string
}
// ======================
// 2. 创建具体原型
// ======================
// ConcretePrototype: 敌人结构体
type Enemy struct {
Name string
Health int
Weapons []string // 引用类型,用于演示深浅拷贝的区别
}
// Clone 实现浅拷贝
func (e *Enemy) Clone() Prototype {
// 浅拷贝:复制结构体,但切片 Weapons 只复制了引用(地址)
fmt.Println("--> Performing Shallow Clone...")
clone := *e // 复制结构体
return &clone
}
// GetInfo 返回敌人信息
func (e *Enemy) GetInfo() string {
return fmt.Sprintf("Enemy{Name: %s, Health: %d, Weapons: %v}", e.Name, e.Health, e.Weapons)
}
// CloneDeep 实现深拷贝
func (e *Enemy) CloneDeep() Prototype {
fmt.Println("--> Performing Deep Clone...")
// 深拷贝:需要手动复制所有引用类型字段
clone := &Enemy{
Name: e.Name,
Health: e.Health,
Weapons: make([]string, len(e.Weapons)), // 创建一个新的切片
}
// 将原切片的元素复制到新切片中
copy(clone.Weapons, e.Weapons)
return clone
}
// ======================
// 3. 客户端使用
// ======================
func main() {
fmt.Println("--- Prototype Pattern in Go ---")
// 1. 创建一个原型敌人
originalEnemy := &Enemy{
Name: "Goblin Archer",
Health: 50,
Weapons: []string{"Short Bow", "Dagger"},
}
fmt.Printf("Original Enemy: %s (Weapons Address: %p)\n", originalEnemy.GetInfo(), originalEnemy.Weapons)
fmt.Println("\n--- Testing Shallow Clone ---")
// 2. 克隆一个新敌人(浅拷贝)
shallowClone := originalEnemy.Clone().(*Enemy)
fmt.Printf("Shallow Clone: %s (Weapons Address: %p)\n", shallowClone.GetInfo(), shallowClone.Weapons)
// 3. 修改克隆对象的引用类型字段
fmt.Println("\nModifying shallow clone's weapon...")
shallowClone.Weapons[0] = "Long Bow"
// 4. 检查原对象是否受影响
fmt.Printf("After modification:\n")
fmt.Printf("Original Enemy: %s\n", originalEnemy.GetInfo()) // 原对象的武器也被修改了!
fmt.Printf("Shallow Clone: %s\n", shallowClone.GetInfo())
fmt.Println("\n========================================\n")
fmt.Println("--- Testing Deep Clone ---")
// 5. 克隆一个新敌人(深拷贝)
deepClone := originalEnemy.CloneDeep().(*Enemy)
fmt.Printf("Deep Clone: %s (Weapons Address: %p)\n", deepClone.GetInfo(), deepClone.Weapons)
// 6. 修改深拷贝对象的引用类型字段
fmt.Println("\nModifying deep clone's weapon...")
deepClone.Weapons[0] = "Crossbow"
// 7. 检查原对象是否受影响
fmt.Printf("After modification:\n")
fmt.Printf("Original Enemy: %s\n", originalEnemy.GetInfo()) // 原对象不受影响
fmt.Printf("Deep Clone: %s\n", deepClone.GetInfo())
}
执行结果
bash
--- Prototype Pattern in Go ---
Original Enemy: Enemy{Name: Goblin Archer, Health: 50, Weapons: [Short Bow Dagger]} (Weapons Address: 0x1400012c030)
--- Testing Shallow Clone ---
--> Performing Shallow Clone...
Shallow Clone: Enemy{Name: Goblin Archer, Health: 50, Weapons: [Short Bow Dagger]} (Weapons Address: 0x1400012c030)
Modifying shallow clone's weapon...
After modification:
Original Enemy: Enemy{Name: Goblin Archer, Health: 50, Weapons: [Long Bow Dagger]}
Shallow Clone: Enemy{Name: Goblin Archer, Health: 50, Weapons: [Long Bow Dagger]}
========================================
--- Testing Deep Clone ---
--> Performing Deep Clone...
Deep Clone: Enemy{Name: Goblin Archer, Health: 50, Weapons: [Short Bow Dagger]} (Weapons Address: 0x1400012c060)
Modifying deep clone's weapon...
After modification:
Original Enemy: Enemy{Name: Goblin Archer, Health: 50, Weapons: [Long Bow Dagger]}
Deep Clone: Enemy{Name: Goblin Archer, Health: 50, Weapons: [Crossbow Dagger]}
从结果中可以清晰地看到:
- 浅拷贝 :
originalEnemy和shallowClone的Weapons字段指向同一个内存地址。修改克隆体的武器,原体的武器也跟着变了。 - 深拷贝 :
originalEnemy和deepClone的Weapons字段指向不同的内存地址。修改克隆体的武器,原体完全不受影响。
总结:原型模式在Go中是一个非常实用的模式,尤其是在需要高效创建复杂对象的场景。
- 核心是
Clone()方法 :为你的结构体实现一个Clone()方法。 - 关键在于拷贝方式 :你必须根据业务需求,决定是使用浅拷贝 还是深拷贝 。
- 如果对象只包含值类型,或者共享引用数据没有问题,浅拷贝简单高效。
- 如果对象包含引用类型,并且新对象需要独立修改,那么必须实现深拷贝。
- Go的实现 :Go没有内置的
Cloneable接口或clone()方法,这使得原型模式的实现更加灵活,但也要求开发者自己处理拷贝的细节。