亦称:克隆、Clone、Prototype

背景

假如有一个对象,希望生成与其完全相同的一个复制品,该如何实现呢?

  1. 新建一个属于相同类的对象
  2. 遍历原始对象的所有成员变量,将成员变量值复制到新对象中。

有个问题,并非所有对象都能通过这种方式进行复制。因为有些对象可能拥有私有成员变量,它们在外部是不可见的。

因此,从"外部"复制对象,并非总是可行的。

直接复制还有其它问题:

  • 必须知道对象所属的类才能创建复制品,所以代码必须依赖该类
  • 有时只知道对象所实现的接口,并不知道具体的类

解决方案

原型模式将克隆过程委派给被克隆的实际对象。

模式为所有支持克隆的对象声明了一个通用接口,该接口提供克隆对象的能力,通常仅包含一个克隆方法

所有的类对克隆方法的实现都非常相似,该方法会创建一个当前类的对象,然后将原始对象所有的成员变量值复制到新建的类中。

支持克隆的对象即为原型

使用原型实例的Clone方法创建新的对象,新的对象属于同一个类,由原型实例拷贝而来。

原型模式常用于创建对象成本较大的情况。这里的拷贝根据实际业务,可以是深拷贝,也可以是浅拷贝。

原型模式

对于Prototype,核心在于对象自身的Clone函数,用于拷贝自己。

是否需要有Prototype接口类,看具体情况。如果需要用到里氏替换,就创建接口类,如果没有用处,就无需创建。

使用场景

  • 业务代码需要复制一些对象,同时又希望业务代码独立于这些对象所属的具体类,可以使用原型模式。
    • 如代码需要处理第三方代码通过接口传递过来的对象,即使不考虑代码耦合的情况,也不能依赖这些对象所属的具体类,可能并不知道它们的具体信息(通过接口传递的)
  • 创建对象成本较大的情况。如需要从DB、硬盘等获取大量数据或者需要经过大量计算等;又或者建立的对象存储的内容是可以被复用,完全重新建一个新的更加耗时。

实现步骤

  1. 创建原型接口,声明克隆方法。
  2. 原型类必须另行定义一个以该类对象为参数的构造函数,该构造函数必须复制参数对象中的所有成员变量值到新建实体中。如果需要修改子类,必须调用父类构造函数,让父类复制其私有成员变量值。
  3. 克隆方法通常只有一行代码:使用new运算符调用原型版本的构造函数。注意,每个类都必须显式重写克隆方法并使用自身类名调用new运算符,否则克隆方法可能会生成父类的对象。
  4. 还可以创建一个中心化原型注册表,用于存储常用原型。

优缺点

优点

  • 可以克隆对象,无需与它们所属的具体类相耦合。
  • 可以克隆预生成原型,避免反复运行初始化代码。
  • 可以更方便地生成复杂对象。
  • 可以用继承以外的方式来处理复杂对象的不同配置。

缺点

  • 克隆包含循环引用的复杂对象可能会非常麻烦。

与其它模式的关系

  • 在许多设计工作的初期都会使用工厂方法模式(较为简单,而且可以更方便地通过子类进行定制),随后演化为使用抽象工厂模式、原型模式或生成器模式(更灵活但更加复杂)。
  • 抽象工厂模式通常基于一组工厂方法,也可以使用原型模式来实现这些类的方法。
  • 原型可用于保存命令模式的历史记录。
  • 大量使用组合模式装饰模式的设计通常可从对于原型的使用中获益。可以通过该模式来复制复杂结构,而非从零开始重新构造。
  • 原型并不基于继承,因此没有继承的缺点。另一方面,原型需要对被复制对象进行复杂的初始化。工厂方法基于继承,但是它不需要初始化步骤。
  • 有时候原型可以作为备忘录模式的一个简化版本,其条件是你需要在历史记录中存储的对象的状态比较简单,不需要链接其他外部资源,或者链接可以方便地重建。
  • 抽象工厂、生成器和原型都可以用单例模式来实现。

示例

1
2
3
4
5
// inode 原型接口
type inode interface {
    print(string)
    clone() inode
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import "fmt"

// file 具体原型
type file struct {
	name string
}

func (f *file) print(indentation string) {
	fmt.Println(indentation + f.name)
}

func (f *file) clone() inode {
	return &file{name: f.name + "_clone"}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import "fmt"

// folder 具体原型
type folder struct {
	children []inode
	name     string
}

func (f *folder) print(indentation string) {
	fmt.Println(indentation + f.name)
	for _, i := range f.children {
		i.print(indentation + indentation)
	}
}

func (f *folder) clone() inode {
	cloneFolder := &folder{name: f.name + "_clone"}
	var tempChildren []inode
	for _, i := range f.children {
		copied := i.clone()
		tempChildren = append(tempChildren, copied)
	}
	cloneFolder.children = tempChildren
	return cloneFolder
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func main() {
	file1 := &file{name: "File1"}
	file2 := &file{name: "File2"}
	file3 := &file{name: "File3"}

	folder1 := &folder{
		children: []inode{file1},
		name:     "Folder1",
	}

	folder2 := &folder{
		children: []inode{folder1, file2, file3},
		name:     "Folder2",
	}
	fmt.Println("\nPrinting hierarchy for Folder2")
	folder2.print("--")

	cloneFolder := folder2.clone()
	fmt.Println("\nPrinting hierarchy for clone Folder")
	cloneFolder.print("--")
}

原型模式就是利用对已有对象(原型)进行复制的方式,来创建新对象,以达到节省创建时间的目的。 拷贝可以选择深拷贝或者浅拷贝。

References

guru