1、博客内容均出自于咕泡学院架构师第三期
2、架构师系列内容:架构师学习笔记(持续更新)
0、原型模式(Prototype Pattern)
指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
原型模式主要适用于以下场景:
- 类初始化消耗资源较多。
- new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)。
- 构造函数比较复杂。
- 循环体中生产大量对象时。
在 Spring 中,原型模式应用得非常广泛。例如 scope=“prototype”,在我们经常用的 JSON.parseObject()也是一种原型模式。
1、浅克隆
一个标准的原型模式代码,应该是这样设计的。先创建原型 Prototype 接口:
package com.jarvisy.demo.pattern.prototype.shallowprototype;
/**
* @author :Jarvisy
* @date :Created in 2020/9/21 0:42
* @description :
*/
public interface Prototype {
Prototype clone();
}
创建具体需要克隆的对象 ConcretePrototype:
package com.jarvisy.demo.pattern.prototype.shallowprototype;
import java.util.List;
/**
* @author :Jarvisy
* @date :Created in 2020/9/21 0:42
* @description :
*/
public class ConcretePrototypeA implements Prototype {
private int age;
private String name;
private List hobbies;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List getHobbies() {
return hobbies;
}
public void setHobbies(List hobbies) {
this.hobbies = hobbies;
}
@Override
public ConcretePrototypeA clone() {
ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
concretePrototype.setAge(this.age);
concretePrototype.setName(this.name);
concretePrototype.setHobbies(this.hobbies);
return concretePrototype;
}
}
创建 Client 对象:
package com.jarvisy.demo.pattern.prototype.shallowprototype;
/**
* @author :Jarvisy
* @date :Created in 2020/9/21 0:44
* @description :
*/
public class Client {
private Prototype prototype;
public Client(Prototype prototype) {
this.prototype = prototype;
}
public Prototype startClone(Prototype concretePrototype) {
return (Prototype) concretePrototype.clone();
}
}
测试代码:
package com.jarvisy.demo.pattern.prototype.shallowprototype;
import java.util.ArrayList;
import java.util.List;
/**
* @author :Jarvisy
* @date :Created in 2020/9/21 0:45
* @description :
*/
public class PrototypeTest {
public static void main(String[] args) {
// 创建一个具体的需要克隆的对象
ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
// 填充属性,方便测试
concretePrototype.setAge(18);
concretePrototype.setName("prototype");
List hobbies = new ArrayList<String>();
concretePrototype.setHobbies(hobbies);
System.out.println(concretePrototype);
// 创建Client对象,准备开始克隆
Client client = new Client(concretePrototype);
ConcretePrototypeA concretePrototypeClone = (ConcretePrototypeA) client.startClone(concretePrototype);
System.out.println(concretePrototypeClone);
System.out.println("克隆对象中的引用类型地址值:" + concretePrototypeClone.getHobbies());
System.out.println("原对象中的引用类型地址值:" + concretePrototype.getHobbies());
System.out.println("对象地址比较:" + (concretePrototypeClone.getHobbies() == concretePrototype.getHobbies()));
}
}
从测试结果看出 hobbies 的引用地址是相同的,意味着复制的不是值,而是引用的地址。这 样 的 话 , 如 果 我 们 修 改 任 意 一 个 对 象 中 的 属 性 值 , concretePrototype 和concretePrototypeCone 的 hobbies 值都会改变。这就是我们常说的浅克隆。只是完整复制了值类型数据,没有赋值引用对象。换言之,所有的引用对象仍然指向原来的对象,显然不是我们想要的结果。
下面我们来看深度克隆继续改造。
2、深克隆
比如 齐天大圣。首先它是一只猴子,有七十二般变化,把一根毫毛就可以吹出千万个泼猴,手里还拿着金箍棒,金箍棒可以变大变小。这就是我们耳熟能详的原型模式的经典体现:
创建原型猴子 Monkey 类:
package com.jarvisy.demo.pattern.prototype.deepprototype;
import java.util.Date;
/**
* @author :Jarvisy
* @date :Created in 2020/9/21 0:49
* @description :
*/
public class Monkey {
public int height;
public int weight;
public Date birthday;
}
创建引用对象金箍棒 Jingubang 类:
package com.jarvisy.demo.pattern.prototype.deepprototype;
import java.io.Serializable;
/**
* @author :Jarvisy
* @date :Created in 2020/9/21 0:50
* @description :
*/
public class JinGuBang implements Serializable {
public float h = 100;
public float d = 10;
public void big() {
this.d *= 2;
this.h *= 2;
}
public void small() {
this.d /= 2;
this.h /= 2;
}
}
创建具体的对象齐天大圣 QiTianDaSheng 类:
package com.jarvisy.demo.pattern.prototype.deepprototype;
import java.io.*;
import java.util.Date;
/**
* @author :Jarvisy
* @date :Created in 2020/9/21 0:50
* @description :
*/
public class QiTianDaSheng extends Monkey implements Cloneable, Serializable {
public JinGuBang jinGuBang;
public QiTianDaSheng() {
//只是初始化
this.birthday = new Date();
this.jinGuBang = new JinGuBang();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return this.deepClone();
}
public Object deepClone() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
QiTianDaSheng copy = (QiTianDaSheng) ois.readObject();
copy.birthday = new Date();
return copy;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public QiTianDaSheng shallowClone(QiTianDaSheng target) {
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
qiTianDaSheng.height = target.height;
qiTianDaSheng.weight = target.height;
qiTianDaSheng.jinGuBang = target.jinGuBang;
qiTianDaSheng.birthday = new Date();
return qiTianDaSheng;
}
}
创建测试类:
package com.jarvisy.demo.pattern.prototype.deepprototype;
/**
* @author :Jarvisy
* @date :Created in 2020/9/21 0:51
* @description :
*/
public class DeepCloneTest {
public static void main(String[] args) {
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
try {
QiTianDaSheng clone = (QiTianDaSheng) qiTianDaSheng.clone();
System.out.println("深克隆:" + (qiTianDaSheng.jinGuBang == clone.jinGuBang));
} catch (Exception e) {
e.printStackTrace();
}
QiTianDaSheng q = new QiTianDaSheng();
QiTianDaSheng n = q.shallowClone(q);
System.out.println("浅克隆:" + (q.jinGuBang == n.jinGuBang));
}
}
3、克隆破坏单例模式
如果我们克隆的目标的对象是单例对象,那意味着,深克隆就会破坏单例。实际上防止克隆破坏单例解决思路非常简单,禁止深克隆便可。要么单例类不实现Cloneable 接口;要么重写 clone()方法,在 clone 方法中返回单例对象即可,具体代码如下:
4、Cloneable源码分析
我们常用的 ArrayList 就实现了 Cloneable 接口:
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}