设计模式-原型模式
原型模式(Prototype Pattern):使用原型实例创建指定创建对象的种类,并通过拷贝这些原型创建新对象。这个模式很好理解,就是ctrl+c,ctrl+v
后做一些小修改。
这里面涉及一个知识点就是深拷贝和浅拷贝的问题,但我相信任何python开发人员都知道copy()
和deepcopy()
的区别,这里就不多说了(有兴趣的可以去看python中这2个函数的实现)。
个人理解当需要多个类对象时,如果要进行很多复杂的、消耗时间的初始化操作,而这些对象之间又仅有少量不同时,可以考虑使用原型模式。
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中new
,make
创建对象的区别:
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)
}
输入如下:
[0 0 0],0x40a0f0
&[],0x40c138
可以看出,new
返回的是指针并且没有初始值,而make
则返回的是引用而且有默认值。此外,make
只能创建slice、map、channel
而new
可以用于所有类型的内存分配。
原型模式代码思路如下:
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仅仅为了演示,生产环境下请使用其他方法实现。
应用场景
- 当产生对象过程比较复杂,初始化需要许多资源时。
- 希望框架原型和产生对象分开时。
- 同一个对象可能会供其他调用者同时调用时。