设计模式-原型模式

文章目录

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

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

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

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

原型模式的UML类图如下:

可以看到原型模式包含三个部分:

  1. 客户端:使用原型对象的地方;
  2. 抽象接口:包含原型的克隆方法
  3. 具体的原型类:实现抽象原型接口。
    因此原型模式的实现如下:
go 复制代码
package main

import "fmt"

// 抽象原型接口
type Prototype interface {
	Clone() Prototype
}

// 具体原型类
type ConcretePrototype struct {
	name string
}

func (p *ConcretePrototype) Clone() Prototype {
	newPrototype := *p
	return &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. 浅拷贝实现

go 复制代码
package main

import "fmt"

type Prototype interface {
	Clone() Prototype
}

type ConcretePrototype struct {
	name   string
	parent *ConcretePrototype
}

func (p *ConcretePrototype) Clone() Prototype {
	newPrototype := *p
	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的值的话,其他对象对应的值也会变:

2.2. 深拷贝实现

2.2.1. 一般实现
go 复制代码
package main

import "fmt"

type Prototype interface {
	Clone() Prototype
}

type ConcretePrototype struct {
	name   string
	parent *ConcretePrototype
}

func (p *ConcretePrototype) Clone() Prototype {
	newPrototype := *p
	if p.parent != nil {
		parent := *p.parent
		newPrototype.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实现
go 复制代码
package main

import (
	"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.Buffer
	encoder := 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实现
go 复制代码
package main

import (
	"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 {
	// 将当前对象编码为 JSON
	data, 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. 图形编辑器中的形状复制

go 复制代码
package main

import (
	"fmt"
)

// Shape 原型接口
type Shape interface {
	Clone() Shape
	Draw()
}

// 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. 游戏中的角色复制

go 复制代码
package main

import (
	"fmt"
)

// Character 原型接口
type Character interface {
	Clone() Character
	Describe()
}

// Enemy 具体的原型类
type Enemy struct {
	Name  string
	HP    int
	Level 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. 配置文件加载以及对象的动态读取

go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
)

// Prototype 接口
type Prototype interface {
	Clone() Prototype
}

// ConfigurableObject 具体的原型类
type ConfigurableObject struct {
	Name   string
	Params 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 []ConfigurableObject
	if err := json.Unmarshal(data, &objects); err != nil {
		panic(err)
	}

	var prototypes []Prototype
	for _, 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)
}
相关推荐
哪 吒7 小时前
最简单的设计模式,抽象工厂模式,是否属于过度设计?
设计模式·抽象工厂模式
Theodore_10227 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
转世成为计算机大神10 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
小乖兽技术11 小时前
23种设计模式速记法
设计模式
小白不太白95012 小时前
设计模式之 外观模式
microsoft·设计模式·外观模式
小白不太白95013 小时前
设计模式之 原型模式
设计模式·原型模式
澄澈i13 小时前
设计模式学习[8]---原型模式
学习·设计模式·原型模式
Domain-zhuo13 小时前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式
小白不太白95020 小时前
设计模式之建造者模式
java·设计模式·建造者模式
菜菜-plus21 小时前
java 设计模式 模板方法模式
java·设计模式·模板方法模式