一、什么是原型模式
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分成员属性都相同),可以利用对已有对象(原型)进行拷贝的方式来创建新对象,以达到节省创建时间的目的,这就是原型模式。
原型模式的使用场景:
如果想要获取一个和原有对象状态一致(数据一致、属性一致)的新对象,但是遇到以下问题的情况下,就可以使用原型模式。
1、对象中的数据需要得到复杂的计算,如排序、计算哈希值等,或者从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取。拷贝对象可以跳过这些过程和开销,直接得到这些数据。
2、对象达到当前状态需要调用很多过程和方法调用,为了避免调用者重复执行这些调用,可以直接拷贝对象一步到位。
那么什么情况下我们会想要获得一个和原有对象一样的新对象呢?
当一个对象A需要提供给对象B和对象C访问(也就是说对象B和对象C都依赖对象A), 而且各个调用者可能需要修改对象A内部的值,如果不想对象B对对象A的变更影响到对象C对对象A的使用,就可以克隆出另一个对象A,让B和C分别依赖两个相互独立的对象A。
二、原型模式如何实现
只要在类中实现一个方法,使得对象调用这个方法后能得到一个和原对象一模一样的新对象,我们就可以说这个类实现了原型模式。
在讨论如何实现之前,需要知道什么是深拷贝和浅拷贝。
我们知道,将一个对象存储到变量中,这个变量其实存储的是对象的引用,或者说存的是对象在内存中的地址。当把这个变量赋值给另一个变量的时候,两个变量存储的都是对象的地址,对象在内存空间中只有一份。同样的道理,如果一个对象A里面既有标量成员,也有对象成员,那么对象A也只是持有对象成员的引用。
假设有一个Row 行对象,里面包含2个字段成员属性,一个是标量成员id,一个是对象成员 Field_A。
对象Row拷贝出一个新对象Row,且两个对象Row的Field_A成员指向的是内存空间的同一个内容(同一个地址),那么说明 Field_A 对象没有被拷贝,依旧只有一份,这种情况就是浅拷贝。
简单的来说,浅拷贝就是值拷贝了表层的对象 Row ,没有拷贝对象Row内部的对象 Field_A。对浅拷贝出来的新对象的变更依旧可能会影响原对象。
如果 拷贝对象Row 的时候,里面的 Field_A 对象也拷贝了一份,那么这种情况就是深拷贝。
浅拷贝的实现方式在各个语言中基本都有方法和函数直接提供,例如python中的copy.copy()、java中Object 类的 clone() 方法、PHP中的clone关键字。
深拷贝则有两种实现方式:
一种是递归的对对象进行浅拷贝,直到要拷贝的最内层对象只包含基本数据类型数据,没有引用对象为止。
另一种最简单粗暴实用,就是直接对对象进行序列化,再对反序列化得到新对象。
原型模式下,基本都是以深拷贝的方式进行对象拷贝,否则对 拷贝对象 的更改依旧会 影响到 原对象,原型模式就失去了意义。
下面以PHP语言为例。
浅拷贝示例
PHP 中可以使用 clone 关键字进行浅拷贝的克隆对象。
一般而言,如果我们在克隆对象之后还希望自动对新对象的属性做一些变更,可以通过重写__clone()方法来做到。
如果在类中设置一个空的,且访问权限为 private 的 __clone() 方法的话,可以起到禁止通过clone关键字克隆的作用。当然,这个方法无法禁止通过 serialize()序列化函数 和 unserialize() 反序列化函数 实现的深拷贝。
深拷贝示例
PHP 中可以使用 serialize()序列化函数 和 unserialize() 反序列化函数实现深拷贝。