原型模式(Prototype Pattern):使用原型实例创建指定创建对象的种类,并通过拷贝这些原型创建新对象。这个模式很好理解,就是ctrl+c,ctrl+v后做一些小修改。

这里面涉及一个知识点就是深拷贝和浅拷贝的问题,但我相信任何python开发人员都知道copy()deepcopy()的区别,这里就不多说了(有兴趣的可以去看python中这2个函数的实现)。

个人理解当需要多个类对象时,如果要进行很多复杂的、消耗时间的初始化操作,而这些对象之间又仅有少量不同时,可以考虑使用原型模式。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import "fmt"

type Cat struct {
name string
age int
}

type Dog struct {
name string
age int
food []string
}

func main() {
a := Cat{name: "tom", age: 3}
b := a
fmt.Println(a == b) // true a和b的值相同
fmt.Println(&b == &a) // false a和b的内存地址不同
b.name = "roy"
fmt.Printf("a:%v \nb:%v \n",a,b)
fmt.Println(a == b) // false a和b的值不同

c := Dog{name: "cola",age:1,food:[]string{"beef","chicken"}}
d := c
d.food[0] = "poke" // 修改d的food会影响到c
fmt.Printf("struct c.food:%p\nstruct d.food:%p\n",&c.food,&d.food)
fmt.Printf("struct c.food[0]:%p\nstruct d.food[0]:%p\n",&c.food[0],&d.food[0]) // 从这里可以看出最终内存地址是一样的

e := c
e.food = append(e.food,"poke") // append方法返回了一个新对象
fmt.Printf("struct e.food:%p\n",&e.food)
fmt.Printf("struct e.food[0]:%p\n",&e.food[0]) // 这里可以看出e的第一个元素已经和c、d不一样了

fmt.Printf("c:%v",c)
fmt.Printf("d:%v",d)
fmt.Printf("e:%v",e)
}

Cat这个类中不包含引用类型,所以直接将a赋值给b后,对b的修改不会影响a。但是Dog类中包含了引用类型,赋值后修改c、d任何一个的food值都会影响另一个。但是如果通过append()方法则是返回了一个新的slice,修改e.food就不会影响c和d了。

常见的引用类型:slice,pointers,maps,functions和channels,所以如果结构中包含这些将一个对象赋值给另一个对象时候要小心。

这里再多说一句关于golang中newmake创建对象的区别:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
)

func main() {
var b = make([]int, 3)
var c = new([]int)
fmt.Printf("%v,%p\n",b,&b)
fmt.Printf("%v,%p\n",c,&c)
}

输入如下:

1
2
[0 0 0],0x40a0f0
&[],0x40c138

可以看出,new返回的是指针并且没有初始值,而make则返回的是引用而且有默认值。此外,make只能创建slice、map、channelnew可以用于所有类型的内存分配。

原型模式代码思路如下:

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
27
28
29
30
31
32
33
package main

import (
"encoding/json"
"fmt"
)

type copy interface {
copy() Dog
}

type Dog struct {
name string
age int
food []string
}

func (d Dog) copy() Dog {
obj := d
food := new([]string)
bytes, _ := json.Marshal(d.food)
_ = json.Unmarshal(bytes, food)
obj.food = *food
return obj
}

func main() {
a := Dog{name: "tom", age: 3, food: []string{"beef","poke"}}
b := a.copy()
b.food[0] = "chicken"
fmt.Printf("a:%v,%p,%p\n",a,&a,&a.food[0])
fmt.Printf("b:%v,%p,%p\n",b,&b,&b.food[0])
}

这里我使用了json包里的序列化函数来模拟deepcopy仅仅为了演示,生产环境下请使用其他方法实现。

应用场景

  1. 当产生对象过程比较复杂,初始化需要许多资源时。
  2. 希望框架原型和产生对象分开时。
  3. 同一个对象可能会供其他调用者同时调用时。