JAVA 设计模式之 原型模式详解
原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式利用的是克隆的原理,创建新的对象,JDK提供的Cloneable 和JSON、springUtil里面的克隆都是一般浅克隆,与之对应的还有深克隆
1、浅克隆
浅克隆也是穿件一个新的对象,不过该对象的属性值是被克隆对象的,如果修改被克隆对象,后者跟着修改。
下面我们用Cloneable写一个简单的浅克隆
import java.util.List;
/**
* @Description TODO
* @Author Bert
* @Date 2019\6\10 0010
*/
public class ShallowClone implements Cloneable{
private String name;
private int age;
private List<String> hobbies;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
//实现 Cloneable 的 clone 方法 属于浅克隆
@Override
public ShallowClone clone() {
ShallowClone shallowClone = null;
try {
shallowClone = (ShallowClone)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return shallowClone;
}
//手写 浅克隆 方法
public ShallowClone spellClone(){
ShallowClone shallowClone = new ShallowClone();
shallowClone.setName(this.name);
shallowClone.setAge(this.age);
shallowClone.setHobbies(this.hobbies);
return shallowClone;
}
}
测试代码:
public static void main(String[] args) {
ShallowClone shallowClone = new ShallowClone();
shallowClone.setName("ZhangSan");
shallowClone.setAge(21);
List<String> list = new ArrayList<>();
list.add("play game");
list.add("Listen music");
shallowClone.setHobbies(list);
//调用Cloneable 的clone方法
ShallowClone clone1 = shallowClone.clone();
//添加喜好
shallowClone.getHobbies().add("fitness");
//调用手写的浅克隆方法
ShallowClone clone2 = shallowClone.spellClone();
System.out.println(shallowClone == clone1);//判读是否为同一个对象
System.out.println(shallowClone == clone2);//判读是否为同一个对象
System.out.println("克隆对象中引用类型地址:"+shallowClone.getHobbies());
System.out.println("克隆对象中引用类型地址:"+clone1.getHobbies());
System.out.println("克隆对象中引用类型地址:"+clone2.getHobbies());
}
运行结果:
false false 克隆对象中引用类型地址:[play game, Listen music, fitness] 克隆对象中引用类型地址:[play game, Listen music, fitness] 克隆对象中引用类型地址:[play game, Listen music, fitness]
从运行结果我们可以看出,克隆对象和原来的对象不是同一个,但对象的属性完全一样,即使改变了其中一个,其他的也会跟着改变。
通过上面spellClone() 方法我们可以看出,浅克隆的整个过程就是,创建一个新的对象,然后新对象的每个值都是由原对象的值,通过 = 进行赋值;
(1)拷贝后获取的是一个独立的对象,和原对象拥有不同的内存地址;
(2)基本元素类型,两者是隔离的,int, Integer, long, Long, char, Charset, byte,Byte, boolean, Boolean, float,Float, double, Double, String
(3)非基本数据类型(如基本容器,其他对象等),只是拷贝了一份引用出去了,实际指向的依然是同一份,例如上的list<String>
一句话总结就是:基本数据类型是值赋值;非基本的就是引用赋值。
2、深克隆
创建一个全新的对象,新的对象内部所有的成员也都是全新的,只是初始化的值已经由被克隆的对象确定,但是他们是两个完全独立的对象,修改是隔离,互不影响。
下面我们来写一个简单的深克隆
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* @Description TODO
* @Author Bert
* @Date 2019\6\10 0010
*/
public class DeepClone implements Serializable{
private String name;
private int age;
private List<String> hobbies;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
// 深克隆 方法 1
public DeepClone deepClone(){
DeepClone deepClone = new DeepClone();
deepClone.setName(this.name);
deepClone.setAge(this.age);
//基本数据类型 重新创建
if(null != this.hobbies){
deepClone.setHobbies(new ArrayList<>(this.hobbies));
}
return deepClone;
}
//深克隆 2 通过 byte字节码 ,这种方式需要 实现 Serializable
public DeepClone deepCloneByte() {
DeepClone dClone = null;
ByteArrayOutputStream bys = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(bys);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bys.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
dClone = (DeepClone)ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return dClone;
}
}
测试代码:
public static void main(String[] args) {
DeepClone deepClone = new DeepClone();
deepClone.setName("ZhangSan");
deepClone.setAge(21);
List<String> list = new ArrayList<>();
list.add("play game");
list.add("Listen music");
deepClone.setHobbies(list);
//调用第一种方式,并修改值
DeepClone deepClone1 = deepClone.deepClone();
deepClone1.getHobbies().add("fitness");
deepClone1.setAge(22);
//调用第二种方式,并修改值
DeepClone deepClone2 = deepClone.deepCloneByte();
deepClone2.getHobbies().add("Climbing mountain");
System.out.println(deepClone == deepClone1);
System.out.println(deepClone == deepClone2);
System.out.println("克隆对象中引用类型地址:"+deepClone.getHobbies());
System.out.println("克隆对象中引用类型地址:"+deepClone1.getHobbies());
System.out.println("克隆对象中引用类型地址:"+deepClone2.getHobbies());
}
运行结果:
false false 克隆对象中引用类型地址:[play game, Listen music] 克隆对象中引用类型地址:[play game, Listen music, fitness] 克隆对象中引用类型地址:[play game, Listen music, Climbing mountain]
以上可以看出深克隆的对象之间修改完全不受影响。
3、原型模式运用场景
(1)类初始化消耗资源较多。
(2)new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
(3)构造函数比较复杂。
(4)循环体中生产大量对象时。
在 Spring 中,原型模式应用得非常广泛。例如 scope=“prototype”,在我们经常用 的 JSON.parseObject()也是一种原型模式。
4、解决深克隆破坏单例模式
如果我们的被克隆对象是单例模式,深克隆就得破坏单例模式。
解决方案:禁止深克隆。一是不实现Cloneable; 二是实现Cloneable ,在clone方法中返回我们的单例。
@Override
protected Object clone() throws CloneNotSupportedException {
return INSTANCE;
}
Cloneable 源码分析:
以ArryList 为例,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);
}
}

浙公网安备 33010602011771号