介绍
目的
用原型示例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除原型。
何时使用:
- 当一个系统应该独立于它的产品创建、构成和表示时。
- 当要实例化的类是在运行时才指定,比如通过动态装载。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能由几个不同状态组合中的一种时,建立相应数目的原型并克隆它们,可能比每次用合适的状态手工实例化该类更方便一些。
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码
- 实现克隆操作,原型类实现Cloneable接口,重写clone()方法。
- 创建一个原型类的实例,使用实例对象的clone()方法就可以快速生成一个和原型对象一样的实例。
优点
- 性能提高
- 逃避构造函数的约束
缺点
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
- 必须实现Cloneable接口
使用场景
- 资源优化场景。
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 对性能和安全有要求的场景。
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 一个对象多个修改者的场景。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
- 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
注意事项
- 与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。一般不做特殊处理时进行的浅拷贝。
- 浅克隆:
- 只克隆了指定对象,对象中的引用变量属性等都没有克隆,最后就是原来的对象A和克隆出来的对象B,它们内部的引用对象都是同样的对象,指向相同的内存地址。所以改一个对象的引用属性时,另一个对象的引用属性也会变化
- 深克隆:
- 克隆对象以及对象内部的引用变量。 只需要修改clone方法即可,在clone方法中,手动给克隆出来的引用对象赋值。
- 浅克隆:
- 原型模式可以破坏单例模式;
- 防止方法:
- 单例模式的对象不实现Cloneable接口
- 重写clone方法的时候,返回getInstance的对象即可
- 防止方法:
new和clone的区别
clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。那么在java语言中,有几种方式可以创建对象呢?
- 使用new操作符创建一个对象
- 使用clone方法复制一个对象
那么这两种方式有什么相同和不同呢?
- new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
- clone在第一步是和new相似的,都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域,填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。
实例
浅克隆
1 | //需要使用clone方法的类 |
输出结果:
1 | A{num=3, str='ABC'} |
可以看出,克隆出的对象与原对象中的变量值一致,但是两个对象的地址并不相同,并不是一个对象的相同引用。 还能发现两个对象的str变量的地址是相同的,就明白这两个str对象的引用地址相同,是同一个对象。
深克隆
1 | public class A implements Cloneable { |
输出结果:
1 | A{num=3, str='ABC'} |
这里与前面不同的是,原对象和克隆出的对象的str变量不再相等,不是同一个引用地址了。
参考
- 本文作者: xczll
- 本文链接: https://xczllgit.github.io/2020/03/09/designPattern/2020-03-09-ProtypePattern/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!