原型模式综述
原型设计模式的本质就是复制一个和自己一样的对象,这个一样指的是数据字段(feild)的一样,并且当原来的对象发生变化的时候,复制出来的对象应该是不能改变的,也就是要进行深度的克隆。java自带了clone的机制,在java的超类中有一个protected clone()方法,其实现了对对象的clone,但是这个clone是浅clone,对于基本数据类型的数据和String类型,是可以直接做到clone效果的,但是对于引用类型的数据,只是copy了其引用而已,如果源对象的引用类型对象的值发生了变化,复制出来的对象里面也会发生变化,也就是做不到真正的深度clone。
下面介绍几种java常见的clone对象的方式,有深度clone,也有浅clone,以便大家对比学习。
java实现clone常用的几种方式
1、java的JDK自带的clone方式
java自带的clone方式属于浅clone,并且要实现Cloneable接口,否则会报不支持clone的异常。
- 为了方便演示,我们首先创建一个数据对象Data,用来模拟引用类型的对象,其中的数据都是基本的类型,实现了Cloneable接口,也实现了jdk的clone()方法
public class Data implements Cloneable, Serializable {
private String name;
private String srtData;
private int data;
public Data() {
}
public Data(String name, String srtData, int data) {
this.name = name;
this.srtData = srtData;
this.data = data;
}
public String getName() {
return name;
}
public Data setName(String name) {
this.name = name;
return this;
}
public String getSrtData() {
return srtData;
}
public Data setSrtData(String srtData) {
this.srtData = srtData;
return this;
}
public int getData() {
return data;
}
public Data setData(int data) {
this.data = data;
return this;
}
@Override
public String toString() {
return "Data{" + "name='" + name + '\'' + ", srtData='" + srtData + '\'' + ", data=" + data + '}';
}
@Override
protected Data clone() throws CloneNotSupportedException {
return (Data) super.clone();
}
}
- 创建一个原型对象类AbstractPrototype,里面模拟了基础数据类型和各种的引用数据类型,也实现了jdk的clone()方法。
public class AbstractPrototype implements Cloneable, Serializable {
private String name;
private int age;
private Data fieldData;
private Map<String, Data> mapData;
private List<Data> listData;
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 Data getFieldData() {
return fieldData;
}
public void setFieldData(Data fieldData) {
this.fieldData = fieldData;
}
public Map<String, Data> getMapData() {
return mapData;
}
public void setMapData(Map<String, Data> mapData) {
this.mapData = mapData;
}
public List<Data> getListData() {
return listData;
}
public void setListData(List<Data> listData) {
this.listData = listData;
}
@Override
public String toString() {
return "Prototype{" + "name='" + name + '\'' + ", age=" + age + ", \nfieldData=" + fieldData + ", \nmapData="
+ mapData.toString() + ", \nlistData=" + listData.toString() + '}';
}
@Override
protected AbstractPrototype clone() throws CloneNotSupportedException {
return (AbstractPrototype) super.clone();
}
}
- 创建一个测试对象,里面写了基础的获得AbstractPrototype 的方法和改变对象前后打印数据的方法,后续在测试不同的方式的时候,只需要新增一个对应的测试方法即可,测试jdk的clone方法是testAbstractPrototype()
public class PrototypeTest {
/**
* 测试Jdk浅clone的方式
*/
@Test
public void testAbstractPrototype() throws Exception {
// 组装原型对象
AbstractPrototype abstractPrototype = generatePrototype(AbstractPrototype.class);
testModifyData(abstractPrototype);
}
private void testModifyData(AbstractPrototype abstractPrototype) throws Exception {
AbstractPrototype clone = abstractPrototype.clone();
System.out.println("-------------------没有修改前的克隆对象-------------------");
System.out.println(clone);
/*System.out.println("-------------------没有修改前的对象-------------------");
System.out.println(abstractPrototype);*/
// 修改数据
abstractPrototype.setName("22");
abstractPrototype.setAge(22);
abstractPrototype.getFieldData().setData(22).setSrtData("22");
abstractPrototype.getMapData().get("mapData").setData(22).setSrtData("22");
abstractPrototype.getListData().get(0).setData(22).setSrtData("22");
System.out.println("-------------------修改之后的克隆对象-------------------");
System.out.println(clone);
/*System.out.println("-------------------修改之后的原对象-------------------");
System.out.println(abstractPrototype);*/
System.out.println("-----------------------------------------------------");
}
private AbstractPrototype generatePrototype(Class<? extends AbstractPrototype> abstractPrototypeClass) {
try {
AbstractPrototype prototype = abstractPrototypeClass.newInstance();
prototype.setName("11");
prototype.setAge(11);
prototype.setFieldData(new Data("fieldData", "11", 11));
Map<String, Data> mapData = new HashMap<>(1);
mapData.put("mapData", new Data("mapData", "11", 11));
prototype.setMapData(mapData);
List<Data> listData = new ArrayList<>(1);
listData.add(new Data("listData", "11", 11));
prototype.setListData(listData);
return prototype;
} catch (Exception e) {
e.printStackTrace();
}
throw new RuntimeException("class is error");
}
}
- 测试结果如下:
-------------------没有修改前的克隆对象-------------------
Prototype{name='11', age=11,
fieldData=Data{name='fieldData', srtData='11', data=11},
mapData={mapData=Data{name='mapData', srtData='11', data=11}},
listData=[Data{name='listData', srtData='11', data=11}]}
-------------------修改之后的克隆对象-------------------
Prototype{name='11', age=11,
fieldData=Data{name='fieldData', srtData='22', data=22},
mapData={mapData=Data{name='mapData', srtData='22', data=22}},
listData=[Data{name='listData', srtData='22', data=22}]}
-----------------------------------------------------
我们可以看到,没有修改前的克隆对象和修改之后的克隆对象的name和age字段并没有发生改变,但是引用对象fieldData、mapData和listData的数据均发生了改变
2、用Apache的BeanUtils.copyProperties()方法实现clone
使用Apache的BeanUtils.copyProperties()方法实现clone,其实实现的是浅克隆,这种方式是不推荐的,但是鉴于很多同学都是在代码里面用到了这个方式,我也特意的整理出来。这种方式实现的clone只适用于一个类中全是基础数据或者string类型的场景。其实现方式如下:
- 我们新增一个类BeanUtilCopyPrototype来继承AbstractPrototype,重写其clone方法
public class BeanUtilCopyPrototype extends AbstractPrototype {
@Override
protected AbstractPrototype clone() throws CloneNotSupportedException {
try {
AbstractPrototype prototype = new AbstractPrototype();
BeanUtils.copyProperties(prototype,this);
return prototype;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- 在测试类PrototypeTest中新增一个测试方法testBeanUtilCopyPrototype()
/**
* 测试BeanUtils的copy进行浅拷贝
* 注意,这个方法是浅拷贝,深拷贝方式比较复杂
*/
@Test
public void testBeanUtilCopyPrototype() throws Exception {
// 组装原型对象
AbstractPrototype abstractPrototype = generatePrototype(BeanUtilCopyPrototype.class);
testModifyData(abstractPrototype);
}
- 测试结果如下:
-------------------没有修改前的克隆对象-------------------
Prototype{name='11', age=11,
fieldData=Data{name='fieldData', srtData='11', data=11},
mapData={mapData=Data{name='mapData', srtData='11', data=11}},
listData=[Data{name='listData', srtData='11', data=11}]}
-------------------修改之后的克隆对象-------------------
Prototype{name='11', age=11,
fieldData=Data{name='fieldData', srtData='22', data=22},
mapData={mapData=Data{name='mapData', srtData='22', data=22}},
listData=[Data{name='listData', srtData='22', data=22}]}
-----------------------------------------------------
从测试结果我们可以看到,没有修改前的克隆对象和修改之后的克隆对象的name和age字段并没有发生改变,但是引用对象fieldData、mapData和listData的数据均发生了改变。因此,BeanUtils实现的copy是浅克隆,其书写方式还没有jdk的优雅。
3、用JDK实现深度clone
使用JDK的方式实现深度clone,其思路就是将引用类型的字段进行特殊的处理,每个字段均实现自己的深度clone的方法,这种方法实现比较繁琐,也不符合开闭原则,因为当新增了引用类型的时候,也需要修改对应的clone()方法,其实现方式如下:
- 新增一个类JdkDeepClonePrototype 继承AbstractPrototype并重写clone()方法
public class JdkDeepClonePrototype extends AbstractPrototype {
@Override
protected AbstractPrototype clone() throws CloneNotSupportedException {
AbstractPrototype prototype = super.clone();
// 需要将引用类型的对象进行分别clone
prototype.setFieldData(super.getFieldData().clone());
Map<String, Data> mapData = new HashMap<>(super.getMapData().size());
for (Map.Entry<String, Data> entry : super.getMapData().entrySet()) {
mapData.put(entry.getKey(), entry.getValue().clone());
}
prototype.setMapData(mapData);
List<Data> listData = new ArrayList<>(super.getListData().size());
for (Data datum : super.getListData()) {
listData.add(datum.clone());
}
prototype.setListData(listData);
return prototype;
}
}
- 在PrototypeTest类中新增一个测试方法testJdkDeepPrototype()
/**
* 测试JDK深度clone的方式
*/
@Test
public void testJdkDeepPrototype() throws Exception {
// 组装原型对象
AbstractPrototype abstractPrototype = generatePrototype(JdkDeepClonePrototype.class);
testModifyData(abstractPrototype);
}
- 执行结果如下
-------------------没有修改前的克隆对象-------------------
Prototype{name='11', age=11,
fieldData=Data{name='fieldData', srtData='11', data=11},
mapData={mapData=Data{name='mapData', srtData='11', data=11}},
listData=[Data{name='listData', srtData='11', data=11}]}
-------------------修改之后的克隆对象-------------------
Prototype{name='11', age=11,
fieldData=Data{name='fieldData', srtData='11', data=11},
mapData={mapData=Data{name='mapData', srtData='11', data=11}},
listData=[Data{name='listData', srtData='11', data=11}]}
-----------------------------------------------------
从结果中我们可以看出,clone出来的对象在修改前后均没有发生变化,实现了深克隆。
4、用IO流的方式实现深克隆
用IO流的方式实现深度clone是个人比较推荐的写法,其具体的思路就是先将对象写入IO流中,然后再读取成新的对象,这种方式是JDK的IO流实现的,需要AbstractPrototype和Data类实现Serializable的接口,其实现方式如下:
- 我们创建一个类IOStreamClonePrototype 继承AbstractPrototype并重写其clone()方法
/**
* 测试用io流的的方式clone
*/
public class IOStreamClonePrototype extends AbstractPrototype {
@Override
protected AbstractPrototype clone() throws CloneNotSupportedException {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
ObjectInputStream objectInputStream = new ObjectInputStream(
new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
return (AbstractPrototype) objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- 在PrototypeTest类中新增一个测试方法testIoStreamPrototype()
/**
* 测试IoStream流clone的方式
*/
@Test
public void testIoStreamPrototype() throws Exception {
// 组装原型对象
AbstractPrototype abstractPrototype = generatePrototype(IOStreamClonePrototype.class);
testModifyData(abstractPrototype);
}
- 执行结果如下:
-------------------没有修改前的克隆对象-------------------
Prototype{name='11', age=11,
fieldData=Data{name='fieldData', srtData='11', data=11},
mapData={mapData=Data{name='mapData', srtData='11', data=11}},
listData=[Data{name='listData', srtData='11', data=11}]}
-------------------修改之后的克隆对象-------------------
Prototype{name='11', age=11,
fieldData=Data{name='fieldData', srtData='11', data=11},
mapData={mapData=Data{name='mapData', srtData='11', data=11}},
listData=[Data{name='listData', srtData='11', data=11}]}
-----------------------------------------------------
从结果可以看出,用IO流实现的clone方法是深克隆
5、用json的方式实现深度克隆
用json的方式实现深度克隆的思路就是先把一个对象转换成json对象,然后再转换回来,就实现了深度克隆,其实现方式如下:
- 新增一个JsonPrototype 类并继承AbstractPrototype,重写其clone()方法
/**
* json的方式实现clone
*/
public class JsonPrototype extends AbstractPrototype{
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
protected AbstractPrototype clone() throws CloneNotSupportedException {
try {
String jsonStr = OBJECT_MAPPER.writeValueAsString(this);
return OBJECT_MAPPER.readValue(jsonStr,JsonPrototype.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- 在PrototypeTest类中新增一个测试方法testJsonPrototype()
/**
* 测试json转换clone的方式
*/
@Test
public void testJsonPrototype() throws Exception {
// 组装原型对象
AbstractPrototype abstractPrototype = generatePrototype(JsonPrototype.class);
testModifyData(abstractPrototype);
}
- 执行结果如下
-------------------没有修改前的克隆对象-------------------
Prototype{name='11', age=11,
fieldData=Data{name='fieldData', srtData='11', data=11},
mapData={mapData=Data{name='mapData', srtData='11', data=11}},
listData=[Data{name='listData', srtData='11', data=11}]}
-------------------修改之后的克隆对象-------------------
Prototype{name='11', age=11,
fieldData=Data{name='fieldData', srtData='11', data=11},
mapData={mapData=Data{name='mapData', srtData='11', data=11}},
listData=[Data{name='listData', srtData='11', data=11}]}
-----------------------------------------------------
从结果可以看出,修改前后对象并没有发生改变
总结
从上面的几种clone方式中可以看出:
- 用jdk实现深度克隆的方式比较繁琐,新增引用类型的属性后需要修改clone()方法,不符合开闭原则,但是如果需要复制的对象属性都是基本的类型或者String类型,实现起来就会比较优雅,新增基础类型或者String的属性也不用修改clone()方法。
- 用IO流和JSON的方式实现的深度克隆不受新增属性的限制,只是执行效率可能不会太高。
后记
个人总结,欢迎转载、评论、批评指正