真实世界的Go设计模式 - 原型模式

*原型模式(Prototype Pattern)是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的。

如果你有一个对象,并希望生成与其完全相同或者类似的一个复制品,你该如何实现呢?首先你必须新建一个属于相同类的对象,或者类似的对象,然后你必须遍历原始对象的所有成员变量,并将成员变量值复制到新对象中。在Go生态圈中,我们常常使用下面的库来做这份工作。

  • jinzhu/copier: 张金柱提供的一个优秀的复制库。
  • switchupcb/copygen: 看Readme用起来就非常的复杂,不过它通过生成代码的方式,而不是reflect方式,理论上来说性能应该更好一些。
  • jmattheis/goverter: 这个看起来比copygen好多了,我们只需需要定义一个转换接口,然后让它自动生成转换的代码。

当然你也可以自己实现转换的方法,最简单的方式就是:

1
2
var newValue = value
var newValue = *valuePointer

但是这种复制也有一些局限性,比如在一些字段复制的值要进行调整,新的对象中有些字段不需要设置等等,所以一般我们有预见对象需要复制(克隆)的话,我们一般会为这个对象类型实现Clone方法,通过这种方式我们实现原型模式。

实际上,Go标准库有很多实现原型模式的例子。

strings.Clone

1
func Clone(s string) string

Clone返回字符串s的一个全新副本。它保证将字符串s复制到一个新的分配空间中,当只保留一个大字符串的一小部分子串时,这可能非常重要。使用Clone可以帮助这些程序减少内存使用或者说避免内存泄露。

maps.Clone

1
func Clone[M ~map[K]V, K comparable, V any](m M) M

Clone返回m的一个副本。这是一个浅克隆:新键和值是使用普通赋值设置的。

http.Transport.Clone

1
func (t *Transport) Clone() *Transport

Clone返回t的导出字段的深度拷贝。

arena.Clone

1
func Clone[T any](s T) T

Clone方法返回一个s的浅拷贝,此拷贝的对象不再在arena中分配。

arena 包还不成熟

slog.Record.Clone

1
func (r Record) Clone() Record

Clone返回一个没有共享状态的记录副本。原记录和克隆记录都可以修改,而不会相互影响。

此外,还有Go标准库还有很多类似的实现原型模式的例子,比如:

  • cmd/distpack/archive.go: Archive.Clone

  • crypto/x509/cert_pool.go: CertPool.Clone

  • text/template/template.go: Template.Clone

  • html/template/template.go: Template.Clone

  • net/http/header.go: Header.Clone

  • net/http/request.go:Request.Clone

  • bytes.Clone

  • crypto/tls/common.go: Config.Clone

  • slices.Clone

还有一些非导出的方法和函数。

总的来说,如果你想实现原型模式,那么最简单的方式就是为你的类型实现一个Clone方法,或者在你的包下实现一个clone函数。