欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > 设计模式-原型模式

设计模式-原型模式

2024/10/24 9:05:59 来源:https://blog.csdn.net/qq_40840571/article/details/141997004  浏览:    关键词:设计模式-原型模式

文章目录

    • 1. 为什么要学习原型模式?
    • 2. 原型模式的结构以及实现
      • 2.1. 浅拷贝实现
      • 2.2. 深拷贝实现
        • 2.2.1. 一般实现
        • 2.2.2. gob实现
        • 2.2.3. json实现
        • 2.2.4. 反射实现
    • 3. 原型模式的优缺点
      • 3.1. 优点
      • 3.2. 缺点
    • 4. 典型举例
      • 4.1. 图形编辑器中的形状复制
      • 4.2. 游戏中的角色复制
      • 4.3. 配置文件加载以及对象的动态读取

1. 为什么要学习原型模式?

原型模式的主要目的就是用来快速克隆对象,而免去其他一系列的初始化操作。原型模式在实际开发中有许多应用场景,如图形编辑器、游戏开发、对象缓存、配置管理等。学习原型模式可以让开发者在这些领域中更容易地实现灵活且高效的设计。

2. 原型模式的结构以及实现

原型模式的UML类图如下:
在这里插入图片描述
可以看到原型模式包含三个部分:

  1. 客户端:使用原型对象的地方;
  2. 抽象接口:包含原型的克隆方法
  3. 具体的原型类:实现抽象原型接口。
    因此原型模式的实现如下:
package mainimport "fmt"// 抽象原型接口
type Prototype interface {Clone() Prototype
}// 具体原型类
type ConcretePrototype struct {name string
}func (p *ConcretePrototype) Clone() Prototype {newPrototype := *preturn &newPrototype
}func (p *ConcretePrototype) SetName(name string) {p.name = name
}func (p *ConcretePrototype) GetName() string {return p.name
}// client,程序入口
func main() {// 创建原型对象prototype := &ConcretePrototype{name: "test",}child1 := prototype.Clone()child2 := prototype.Clone()fmt.Printf("child1 name: %s\n", child1.(*ConcretePrototype).GetName())fmt.Printf("child2 name: %s\n", child2.(*ConcretePrototype).GetName())
}

可以看到在创建prototype对象的基础上,可以快速的初始化其他两个对象。不需要考虑其他参数的值。
通过观察Clone代码可以知道,如果类中存在引用对象,比如指针,引用等等,会导致深浅拷贝的问题。如果不包含这类属性,是不会存在问题的。具体深浅拷贝的区别可以看https://www.cnblogs.com/yuwenjing0727/p/13607651.html

2.1. 浅拷贝实现

package mainimport "fmt"type Prototype interface {Clone() Prototype
}type ConcretePrototype struct {name   stringparent *ConcretePrototype
}func (p *ConcretePrototype) Clone() Prototype {newPrototype := *preturn &newPrototype
}func (p *ConcretePrototype) SetName(name string) {p.name = name
}func (p *ConcretePrototype) GetName() string {return p.name
}func main() {parent := &ConcretePrototype{name: "parent",}// 创建原型对象prototype := &ConcretePrototype{name:   "child",parent: parent,}child1 := prototype.Clone()child2 := prototype.Clone()// 打印原型对象的名称以及父对象的地址fmt.Printf("child1 name: %s, parent address: %p\n", child1.(*ConcretePrototype).GetName(), child1.(*ConcretePrototype).parent)fmt.Printf("child2 name: %s, parent address: %p\n", child2.(*ConcretePrototype).GetName(), child2.(*ConcretePrototype).parent)
}

运行结果如图所示,可以看到这两个对象的parent属性都是一致的,也就是浅克隆,这样如果在修改其中一个对象的parent的值的话,其他对象对应的值也会变:
在这里插入图片描述

2.2. 深拷贝实现

2.2.1. 一般实现
package mainimport "fmt"type Prototype interface {Clone() Prototype
}type ConcretePrototype struct {name   stringparent *ConcretePrototype
}func (p *ConcretePrototype) Clone() Prototype {newPrototype := *pif p.parent != nil {parent := *p.parentnewPrototype.parent = &parent}return &newPrototype
}func (p *ConcretePrototype) SetName(name string) {p.name = name
}func (p *ConcretePrototype) GetName() string {return p.name
}func main() {parent := &ConcretePrototype{name: "parent",}// 创建原型对象prototype := &ConcretePrototype{name:   "child",parent: parent,}child1 := prototype.Clone()child2 := prototype.Clone()// 打印原型对象的名称以及父对象的地址fmt.Printf("child1 name: %s, parent address: %p\n", child1.(*ConcretePrototype).GetName(), child1.(*ConcretePrototype).parent)fmt.Printf("child2 name: %s, parent address: %p\n", child2.(*ConcretePrototype).GetName(), child2.(*ConcretePrototype).parent)
}

运行结果如下:
在这里插入图片描述
但是这种实现方法涉及到递归遍历的问题,也就是说如果parent下面还有指针对象,又或者parent的兄弟属性还有其他的指针对象,那么这种实现方法将会变得非常复杂。所以就有了另外三种实现方法:gob包、json和反射实现

2.2.2. gob实现
package mainimport ("bytes""encoding/gob""fmt"
)type Prototype interface {Clone() Prototype
}type ConcretePrototype struct {Name   string             // 改为大写,成为导出字段Parent *ConcretePrototype // 改为大写,成为导出字段
}// Clone 使用 gob 来实现深拷贝
func (p *ConcretePrototype) Clone() Prototype {newPrototype := &ConcretePrototype{}deepCopy(p, newPrototype)return newPrototype
}func (p *ConcretePrototype) SetName(name string) {p.Name = name
}func (p *ConcretePrototype) GetName() string {return p.Name
}func deepCopy(src, dst interface{}) {var buffer bytes.Bufferencoder := gob.NewEncoder(&buffer)if err := encoder.Encode(src); err != nil {panic(err)}decoder := gob.NewDecoder(&buffer)if err := decoder.Decode(dst); err != nil {panic(err)}
}func main() {parent := &ConcretePrototype{Name: "parent",}// 创建原型对象prototype := &ConcretePrototype{Name:   "child",Parent: parent,}child1 := prototype.Clone()child2 := prototype.Clone()// 打印原型对象的名称以及父对象的地址fmt.Printf("child1 name: %s, parent address: %p\n", child1.(*ConcretePrototype).GetName(), child1.(*ConcretePrototype).Parent)fmt.Printf("child2 name: %s, parent address: %p\n", child2.(*ConcretePrototype).GetName(), child2.(*ConcretePrototype).Parent)// 修改 parent 的 name,检查 child1 和 child2 是否受影响parent.SetName("new parent")fmt.Printf("After modifying parent...\n")fmt.Printf("child1 name: %s, parent address: %p\n", child1.(*ConcretePrototype).GetName(), child1.(*ConcretePrototype).Parent)fmt.Printf("child2 name: %s, parent address: %p\n", child2.(*ConcretePrototype).GetName(), child2.(*ConcretePrototype).Parent)fmt.Printf("parent name: %s\n", parent.GetName())
}

运行结果如下:
在这里插入图片描述

2.2.3. json实现
package mainimport ("encoding/json""fmt"
)type Prototype interface {Clone() Prototype
}type ConcretePrototype struct {name   string             // 小写字段Parent *ConcretePrototype `json:"parent"` // 使用 JSON 标签
}// Clone 使用 JSON 进行深拷贝
func (p *ConcretePrototype) Clone() Prototype {// 将当前对象编码为 JSONdata, err := json.Marshal(p)if err != nil {panic(err)}// 解码到新的实例newPrototype := &ConcretePrototype{}if err := json.Unmarshal(data, newPrototype); err != nil {panic(err)}return newPrototype
}func (p *ConcretePrototype) SetName(name string) {p.name = name
}func (p *ConcretePrototype) GetName() string {return p.name
}func main() {parent := &ConcretePrototype{name: "parent",}// 创建原型对象prototype := &ConcretePrototype{name:   "child",Parent: parent,}child1 := prototype.Clone()child2 := prototype.Clone()// 打印原型对象的名称以及父对象的地址fmt.Printf("child1 name: %s, parent address: %p\n", child1.(*ConcretePrototype).GetName(), child1.(*ConcretePrototype).Parent)fmt.Printf("child2 name: %s, parent address: %p\n", child2.(*ConcretePrototype).GetName(), child2.(*ConcretePrototype).Parent)// 修改 parent 的 name,检查 child1 和 child2 是否受影响parent.SetName("new parent")fmt.Printf("After modifying parent...\n")fmt.Printf("child1 name: %s, parent address: %p\n", child1.(*ConcretePrototype).GetName(), child1.(*ConcretePrototype).Parent)fmt.Printf("child2 name: %s, parent address: %p\n", child2.(*ConcretePrototype).GetName(), child2.(*ConcretePrototype).Parent)fmt.Printf("parent name: %s\n", parent.GetName())
}

运行结果如下:
在这里插入图片描述
可以看到上面两种方法的实现,都需要类对象的首字母开头大写,这样可能会有参数泄漏的风险,下面可以看看反射的实现,比较复杂。

2.2.4. 反射实现

具体可以参考这个链接:
https://github.com/mohae/deepcopy/blob/master/deepcopy.go

3. 原型模式的优缺点

原型模式是一种创建对象的设计模式,主要通过复制现有对象来创建新对象,而不是通过实例化新对象的类。下面是原型模式的具体优缺点:

3.1. 优点

  1. 性能优化:在某些情况下,创建新对象可能是一个昂贵的操作。原型模式允许通过复制现有对象来减少对象创建的时间和资源开销,尤其是在对象的构造过程复杂时。
  2. 简化对象创建:当需要创建许多相似对象时,使用原型模式可以避免重复的构造代码。通过简单地复制已有对象,可以快速创建新对象,降低代码重复性。
  3. 支持复杂对象结构:原型模式能够轻松复制包含复杂结构(如树或图)的对象。这使得在处理复杂的数据结构时,原型模式显得尤为有用。
  4. 动态配置和扩展:原型模式允许在运行时根据配置动态创建对象。这种灵活性特别适用于需要根据用户输入或外部配置(如 JSON 文件)生成对象的场景。
  5. 避免类的膨胀:通过使用原型模式,可以减少类的数量,避免过度的子类化。原型可以包含不同的配置和状态,从而减少继承层次的复杂性。
  6. 实现深拷贝:原型模式可以实现对象的深拷贝,确保复制的对象与原始对象之间是完全独立的。这对于需要状态隔离的场景非常重要。

3.2. 缺点

  1. 实现复杂性:实现原型模式时,特别是在需要深拷贝的情况下,开发者需要编写额外的代码来确保所有嵌套对象都被正确复制。这可能导致实现复杂性增加。
  2. 内存消耗:在某些情况下,复制对象可能会导致额外的内存消耗。如果对象非常大或者嵌套层级较深,复制对象可能会占用大量内存。
  3. 依赖于可克隆性:原型模式的有效性依赖于对象的可克隆性。如果对象中的某些字段或属性不支持克隆(如某些资源句柄),则可能会导致复制不完整或失败。
  4. 难以维护:如果原型对象的结构发生变化(如添加、删除字段),则需要确保所有的复制方法都得到相应更新,这可能会导致维护工作量增加。

4. 典型举例

4.1. 图形编辑器中的形状复制

package mainimport ("fmt"
)// Shape 原型接口
type Shape interface {Clone() ShapeDraw()
}// Circle 具体的原型类
type Circle struct {X, Y, Radius int
}// Clone 方法实现
func (c *Circle) Clone() Shape {return &Circle{X: c.X, Y: c.Y, Radius: c.Radius}
}// Draw 方法实现
func (c *Circle) Draw() {fmt.Printf("Drawing Circle at (%d, %d) with radius %d\n", c.X, c.Y, c.Radius)
}// Rectangle 具体的原型类
type Rectangle struct {X, Y, Width, Height int
}// Clone 方法实现
func (r *Rectangle) Clone() Shape {return &Rectangle{X: r.X, Y: r.Y, Width: r.Width, Height: r.Height}
}// Draw 方法实现
func (r *Rectangle) Draw() {fmt.Printf("Drawing Rectangle at (%d, %d) with width %d and height %d\n", r.X, r.Y, r.Width, r.Height)
}func main() {// 创建原型对象circle1 := &Circle{X: 10, Y: 10, Radius: 5}rectangle1 := &Rectangle{X: 20, Y: 20, Width: 15, Height: 10}// 复制原型对象circle2 := circle1.Clone()rectangle2 := rectangle1.Clone()// 绘制原型对象circle1.Draw()rectangle1.Draw()// 绘制复制的对象circle2.Draw()rectangle2.Draw()
}

4.2. 游戏中的角色复制

package mainimport ("fmt"
)// Character 原型接口
type Character interface {Clone() CharacterDescribe()
}// Enemy 具体的原型类
type Enemy struct {Name  stringHP    intLevel int
}// Clone 方法实现
func (e *Enemy) Clone() Character {return &Enemy{Name: e.Name, HP: e.HP, Level: e.Level}
}// Describe 方法实现
func (e *Enemy) Describe() {fmt.Printf("Enemy: %s, HP: %d, Level: %d\n", e.Name, e.HP, e.Level)
}func main() {// 创建原型对象enemy1 := &Enemy{Name: "Goblin", HP: 50, Level: 1}// 复制原型对象enemy2 := enemy1.Clone()// 描述原型对象enemy1.Describe()// 描述复制的对象enemy2.Describe()// 修改复制对象的属性enemy2.(*Enemy).Name = "Orc"enemy2.(*Enemy).HP = 80// 描述修改后的复制对象enemy2.Describe()// 原型对象依然保持不变enemy1.Describe()
}

4.3. 配置文件加载以及对象的动态读取

package mainimport ("encoding/json""fmt""io/ioutil""os"
)// Prototype 接口
type Prototype interface {Clone() Prototype
}// ConfigurableObject 具体的原型类
type ConfigurableObject struct {Name   stringParams map[string]interface{}
}// Clone 方法实现
func (co *ConfigurableObject) Clone() Prototype {// 深拷贝 Params 字典newParams := make(map[string]interface{})for k, v := range co.Params {newParams[k] = v}return &ConfigurableObject{Name: co.Name, Params: newParams}
}// 从 JSON 配置文件创建对象
func createObjectsFromConfig(filename string) []Prototype {file, err := os.Open(filename)if err != nil {panic(err)}defer file.Close()data, err := ioutil.ReadAll(file)if err != nil {panic(err)}var objects []ConfigurableObjectif err := json.Unmarshal(data, &objects); err != nil {panic(err)}var prototypes []Prototypefor _, obj := range objects {prototypes = append(prototypes, obj.Clone())}return prototypes
}func main() {// 假设配置文件 config.json 的内容如下:// [//     {//         "Name": "Object1",//         "Params": {//             "Param1": "Value1",//             "Param2": 42//         }//     },//     {//         "Name": "Object2",//         "Params": {//             "ParamA": "ValueA",//             "ParamB": 100//         }//     }// ]// 创建原型对象prototypes := createObjectsFromConfig("config.json")// 描述每个原型对象for _, prototype := range prototypes {obj := prototype.(*ConfigurableObject)fmt.Printf("Name: %s, Params: %+v\n", obj.Name, obj.Params)}// 复制一个原型对象clonedObject := prototypes[0].Clone()clonedObject.(*ConfigurableObject).Name = "ClonedObject"// 描述复制的对象fmt.Printf("Cloned Object: %+v\n", clonedObject)
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com