一、概念
原型模式(Prototype Pattern):用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。(创建型模式。简单来说,就是用于复制对象)
在Spring中,原型模式应用得非常广泛。例如 scope=“prototype”,我们经常用的JSON.parseObject()也是一种原型模式。
我们拿电脑中的复制粘贴的例子来演示一下原型模式:
上面这张图已经很明显了,我们首先需要一个文件,该文件一定要有可以被克隆的功能,那么我们创建这个文件后,就可以通过它克隆无数个。
下面是原型模式的类结构图:
二、适用场景
- 类初始化消耗资源较多
- new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
- 构造方法比较复杂
- 循环体中生产大量对象时
三、模拟原型模式
① 先声明一个克隆自身的原型Prototype接口:
② 创建具体需要克隆的对象ConcretePrototype:
③ 定义一个Client类,用于测试克隆对象:
上面实际上,把new用代码隐藏了,本质上对象的产生还是靠new产生的,只是耐看了一些,具备了原型模式的形态,但产生的对象并不能直接复制对象的当前运行状态(每拷贝一次对象,需要走一次构造方法),所以需要做一些改进。
四、真正原型模式
- 只有实现了Cloneable这个接口的类才可以被拷贝
- 拷贝得到的是一个新的对象,但是这个新对象的内容是拷贝对象的当前运行时内容
- 在拷贝对象的过程中,没有调用构造方法(new的时候,JVM要走一趟类加载流程,这个流程非常麻烦,会调用构造方法,再把最后生成的对象放到堆中。而Object类的clone()方法原理:JVM从堆内存中以二进制流的方式进行拷贝,重新分配一个内存块,将对象当前内存状态拷贝到新开辟的内存中)
- 拷贝分为浅拷贝和深拷贝,主要区别在于是否支持对象引用类型的成员变量的拷贝。
1、浅拷贝:只会拷贝基本数据类型(也包括String),对于对象属性只拷贝其中的引用地址。
例子:① 定义一个Student类,实现Cloneable接口,重写clone()方法。
② 测试代码:
③ 运行结果:
分析:从测试结果可以看出,String[]对象没有被拷贝,拷贝的只是对象引用的地址。因此,如果我们修改任意一个对象类型的成员变量的属性值,比如上图中的hobbies值,则所有拷贝的hobbies值都会改变,这就是浅拷贝。
下面这张图,对象引用的地址指向堆内存中的对象实例:
2、深拷贝:不仅能够拷贝基本数据类型,还能拷贝对象引用类型(包括数组、容器、引用对象等)。
例子:① 还是原来的Student类,现在对clone()方法中的逻辑做一些修改。
② 测试代码:
③ 运行结果:
分析:从测试结果可以看出,String[]对象在内存中被拷贝。因此,如果要实现深拷贝,必须将原型中的数组、容器对象、引用对象等另行拷贝(这些引用对象也要实现Cloneable接口)。
五、拷贝破坏单例模式
提问:如果我们深拷贝的目标对象是单例对象,那意味着,深拷贝就会破坏单例,怎么办?
实际上防止拷贝破坏单例的解决思路很简单:
① 禁止深拷贝即可
② 单例类不实现Cloneable接口
③ 如果非要实现Cloneable接口,那么可以重写clone()方法,在方法中返回单例对象即可,如下:
六、在源码中的应用
我们常用的ArrayList就实现了Cloneable接口,重写clone()方法,源码如下: