设计模式-原型模式

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

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

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

prototype.png

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创建对象的区别:

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、channelnew可以用于所有类型的内存分配。

原型模式代码思路如下:

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. 同一个对象可能会供其他调用者同时调用时。