如果一个类的有非常多的属性,层级还很深。每次构造起来,不管是直接构造还是用建造者模式,都要对太多属性进行复制,那么有没有一种好的方式让我们创建太的时候使用体验更好一点呢? 今天的文章里就给大家介绍一种设计模式,来解决这个问题。
这篇内容要说的是创造型设计模式里的原型模式,如果写过点 JS 代码的话,大家可能听说过原型链这么个东西,原型模式在 JavaScript 实现里确实广泛应用,它那个面向对象跟 Java、C++ 这些语言的面向对象的实现方式还不太一样,继承其实是通过原型克隆出来,在拷贝出来的原型的基础上再继续添加或者修改来实现的。
原型模式
通过复制、拷贝或者叫克隆已有对象的方式来创建新对象的设计模式叫做原型模式,被拷贝的对象也被称作原型对象。
原型对象按照惯例,会暴露出一个 Clone 方法,给外部调用者一个机会来从自己这里“零成本”的克隆出一个新对象。
这里的“零成本”说的是,调用者啥都不用干,干等着,原型对象在 Clone 方法里自己克隆出自己,给到调用者,所以按照这个约定所有原型对象都要实现一个 Clone 方法。
type Prototype interface {
Clone() SpecificType
}
这里我们用UML类图描述一下原型模式中各角色拥有的行为以及它们之间的关系
至于原型对象克隆自己的时候用的是深拷贝还是浅拷贝?可以先理解成是都用深拷贝,等完全掌握这种思想后,可以再根据实际情况,比如为了节省空间、以及减少编写克隆方法的复杂度时可以两者综合使用。
原型模式更多的是阐述一种编程模式,并没有限制我们用什么方式实现。比如下面这个深拷贝和浅拷贝结合使用的例子。
package prototype
import (
"encoding/json"
"time"
)
// Keyword 搜索关键字
type Keyword struct {
word string
visit int
UpdatedAt *time.Time
}
// Clone 这里使用序列化与反序列化的方式深拷贝
func (k *Keyword) Clone() *Keyword {
var newKeyword Keyword
b, _ := json.Marshal(k)
json.Unmarshal(b, &newKeyword)
return &newKeyword
}
// Keywords 关键字 map
type Keywords map[string]*Keyword
// Clone 复制一个新的 keywords
// updatedWords: 需要更新的关键词列表,由于从数据库中获取数据常常是数组的方式
func (words Keywords) Clone(updatedWords []*Keyword) Keywords {
newKeywords := Keywords{}
for k, v := range words {
// 这里是浅拷贝,直接拷贝了地址
newKeywords[k] = v
}
// 替换掉需要更新的字段,这里用的是深拷贝
for _, word := range updatedWords {
newKeywords[word.word] = word.Clone()
}
return newKeywords
}
使用原型模式的目的主要是为了节省创建对象所花费的时间和资源消耗,提升性能。
还有一点就是,比如全局配置对象这种也可以当成原型对象,如果不想让程序在运行时修改初始化好的原型对象,导致影响其他线程的程序执行的时候,也可以用原型模式快速拷贝出一份,再在副本上做运行时自定义修改。