原型模式——java实现原型模式的几种写法

原型模式综述

  原型设计模式的本质就是复制一个和自己一样的对象,这个一样指的是数据字段(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的异常。

  1. 为了方便演示,我们首先创建一个数据对象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();
    }
}
  1. 创建一个原型对象类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();
    }
}
  1. 创建一个测试对象,里面写了基础的获得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");
    }
}
  1. 测试结果如下:
-------------------没有修改前的克隆对象-------------------
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类型的场景。其实现方式如下:

  1. 我们新增一个类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;
    }
}
  1. 在测试类PrototypeTest中新增一个测试方法testBeanUtilCopyPrototype()
    /**
     * 测试BeanUtils的copy进行浅拷贝
     * 注意,这个方法是浅拷贝,深拷贝方式比较复杂
     */
    @Test
    public void testBeanUtilCopyPrototype() throws Exception {
        // 组装原型对象
        AbstractPrototype abstractPrototype = generatePrototype(BeanUtilCopyPrototype.class);
        testModifyData(abstractPrototype);
    }
  1. 测试结果如下:
-------------------没有修改前的克隆对象-------------------
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()方法,其实现方式如下:

  1. 新增一个类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;
    }
}
  1. 在PrototypeTest类中新增一个测试方法testJdkDeepPrototype()
    /**
     * 测试JDK深度clone的方式
     */
    @Test
    public void testJdkDeepPrototype() throws Exception {
        // 组装原型对象
        AbstractPrototype abstractPrototype = generatePrototype(JdkDeepClonePrototype.class);
        testModifyData(abstractPrototype);
    }
  1. 执行结果如下
-------------------没有修改前的克隆对象-------------------
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的接口,其实现方式如下:

  1. 我们创建一个类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;
    }
}
  1. 在PrototypeTest类中新增一个测试方法testIoStreamPrototype()
    /**
     * 测试IoStream流clone的方式
     */
    @Test
    public void testIoStreamPrototype() throws Exception {
        // 组装原型对象
        AbstractPrototype abstractPrototype = generatePrototype(IOStreamClonePrototype.class);
        testModifyData(abstractPrototype);
    }
  1. 执行结果如下:
-------------------没有修改前的克隆对象-------------------
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对象,然后再转换回来,就实现了深度克隆,其实现方式如下:

  1. 新增一个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;
    }
}
  1. 在PrototypeTest类中新增一个测试方法testJsonPrototype()
    /**
     * 测试json转换clone的方式
     */
    @Test
    public void testJsonPrototype() throws Exception {
        // 组装原型对象
        AbstractPrototype abstractPrototype = generatePrototype(JsonPrototype.class);
        testModifyData(abstractPrototype);
    }
  1. 执行结果如下
-------------------没有修改前的克隆对象-------------------
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的方式实现的深度克隆不受新增属性的限制,只是执行效率可能不会太高。

后记
  个人总结,欢迎转载、评论、批评指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值