原型模式

一、概念

原型模式(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()方法,源码如下:

posted @ 2020-03-15 16:26  Zeki_Chen  阅读(313)  评论(0编辑  收藏  举报