创建型设计模式-原型模式

什么是原型模式

原型是一种创建型设计模式,使 你能够复制已有对象,而又无需 使代码依赖它们所属的类

  1. 原型(Prototype)接口将对克隆方法进行声明。在绝大多数 情况下,其中只会有一个名为 clone 克隆 的方法

  2. 具体原型(Concrete Prototype)类将实现克隆方法。除了将 原始对象的数据复制到克隆体中之外,该方法有时还需处理 克隆过程中的极端情况,例如克隆关联对象和梳理递归依赖 等等

  3. 客户端(Client)可以复制实现了原型接口的任何对象

  • 原型注册表(Prototype Registry)提供了一种访问常用原型 的简单方法,其中存储了一系列可供随时复制的预生成对象。 最简单的注册表原型是一个 名称 → 原型 的哈希表。 但如 果需要使用名称以外的条件进行搜索,你可以创建更加完善 的注册表版本

Example

原型接口

package main

type Inode interface {
print(string)
clone() Inode
}

具体原型A

package main

import "fmt"

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"}
}

具体原型B

package main

import "fmt"

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 {
copy := i.clone()
tempChildren = append(tempChildren, copy)
}
cloneFolder.children = tempChildren
return cloneFolder
}

客户端代码

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(" ")
}

适用场景

  1. 如果你需要复制一些对象,同时又希望代码独立于这些对象 所属的具体类,可以使用原型模式。
  • 这一点考量通常出现在代码需要处理第三方代码通过接口传 递过来的对象时。即使不考虑代码耦合的情况,你的代码也 不能依赖这些对象所属的具体类,因为你不知道它们的具体 信息
  1. 如果子类的区别仅在于其对象的初始化方式,那么你可以使 用该模式来减少子类的数量。别人创建这些子类的目的可能 是为了创建特定类型的对象
  • 在原型模式中,你可以使用一系列预生成的、各种类型的对 象作为原型。 客户端不必根据需求对子类进行实例化,只需找到合适的原 型并对其进行克隆即可。

优缺点

优点

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

缺点

  • 可以用继承以外的方式来处理复杂对象的不同配置

与其他模式的关系

  • 在许多设计工作的初期都会使用工厂方法(较为简单,而且 可以更方便地通过子类进行定制),随后演化为使用抽象工 厂、原型或生成器(更灵活但更加复杂)。

  • 抽象工厂模式通常基于一组工厂方法,但你也可以使用原型 模式来生成这些类的方法。

  • 原型可用于保存命令的历史记录。 •

  • 大量使用组合和装饰的设计通常可从对于原型的使用中获益

  • 你可以通过该模式来复制复杂结构,而非从零开始重新构造。 •

  • 原型并不基于继承,因此没有继承的缺点。另一方面,原型 需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。

  • 有时候原型可以作为备忘录的一个简化版本,其条件是你需 要在历史记录中存储的对象的状态比较简单,不需要链接其 他外部资源,或者链接可以方便地重建。

  • 抽象工厂、生成器和原型都可以用单例来实现