0
点赞
收藏
分享

微信扫一扫

架构师成长之路-设计模式-07.原型模式、克隆模式、java属性复制

言午栩 2022-02-19 阅读 59

原型模式

原型模式解决的是大量属性复制问题。
本文将介绍几种深度属性复制方式、几种浅度属性复制方式。
本文源码地址将在文末给出。

未使用原型模式之前的代码

public static void testCloneStupid() {
        PrototypePerson person = new PrototypePerson()
                .setId("10001")
                .setAge(18)
                .setName("初见")
                .setHobbies(CollUtil.newArrayList("吃奶", "睡觉"));

        // 手动复制属性
        PrototypePerson clonePerson = new PrototypePerson();
        clonePerson.setName(person.getName());
        clonePerson.setAge(person.getAge());
        clonePerson.setId(person.getId());
        // .... 还有很多属性需要复制
        clonePerson.setHobbies(person.getHobbies());

        clonePerson.getHobbies().add("打豆豆");
        System.out.println("person:" + person.toString());
        System.out.println("clonePerson:" + clonePerson.toString());
    }

代码规范工整,有什么问题 ?大家想象一下,假如有100+个属性需要赋值,怎么办?原型模式解决的就是这样大量属性复制引起的问题。

原型模式改造后的代码

public class PrototypePerson implements Cloneable, Serializable {
    private String id;
    private String name;
    private Integer age;
    private List<String> hobbies;
    @Override
    public PrototypePerson clone() {
        try {
            // jdk自带的clone必须实现 Cloneable, Serializable 两个接口
            return (PrototypePerson) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

调用者使用代码

public static void testJdkClone() {
        PrototypePerson person = new PrototypePerson()
                .setId("10001")
                .setAge(18)
                .setName("初见")
                .setHobbies(CollUtil.newArrayList("吃奶", "睡觉"));
        // jdk自带的克隆
        PrototypePerson clonePerson = person.clone();

        clonePerson.getHobbies().add("打豆豆");
        System.out.println("person:" + person.toString());
        System.out.println("clonePerson:" + clonePerson.toString());
    }

输出如下:
在这里插入图片描述
使用原型模式,大大简化了调用者的代码。将实现细节写到了实体类里面。
但是问题来了,复制对类型的属性时,复制的是引用,所以修改person 的 hobbies 时会同步修改 clonePerson 的属性。

深度复制与浅复制

浅复制: 当复制对象类型的属性时,复制的是对象的引用(像jdk的clone是浅复制)。
深复制: 完全复制源对象的属性,包括对象类型的属性。

深度复制 之 字节流序列化方式

public static <T> T deepClone(T obj) {
        if (null == obj) return null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            Object o = ois.readObject();
            return (T) o;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

调用代码:

public static void testDeepClone() {
        PrototypePerson person = new PrototypePerson()
                .setId("10001")
                .setAge(18)
                .setName("初见")
                .setHobbies(CollUtil.newArrayList("吃奶", "睡觉"));
        PrototypePerson clonePerson = PrototypeUtils.deepClone(person);

        clonePerson.getHobbies().add("打豆豆");
        System.out.println("person:" + person.toString());
        System.out.println("clonePerson:" + clonePerson.toString());

    }

输出结果:
在这里插入图片描述
字节流序列化深度复制 方式常用,性能高。

深度复制 之 Json序列化方式

public static <T> T deepCloneByJson(T obj) {
     String jsonString = JSON.toJSONString(obj);
     Object object = JSON.parseObject(jsonString, obj.getClass());
     return (T) object;
 }

Json序列化深度复制 方式编码简单,小白也能记住 ~
这里就不给测试代码了,输出结果同上。

浅复制 之 BeanUtils

// 这里使用的是 Spring的BeanUtils
// import org.springframework.beans.BeanUtils;

public static void testBeanUtilsClone() {
        PrototypePerson person = new PrototypePerson()
                .setId("10001")
                .setAge(18)
                .setName("初见")
                .setHobbies(CollUtil.newArrayList("吃奶", "睡觉"));
        PrototypePerson clonePerson = new PrototypePerson();
        // 借助BeanUtils辅助属性
        BeanUtils.copyProperties(person, clonePerson);

        clonePerson.getHobbies().add("打豆豆");
        System.out.println("person:" + person.toString());
        System.out.println("clonePerson:" + clonePerson.toString());
    }

浅复制 之 jdk Object.clone() 方式

public class PrototypePerson implements Cloneable, Serializable {
    private String id;
    // ....
    @Override
    public PrototypePerson clone() {
        try {
            // 使用Jdk的浅复制
            return (PrototypePerson) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

特别交代

  1. 并非所有原型模式都需要深度复制,看实际的业务场景。
  2. 原型模式的本意是较少手动复制大量属性的愚蠢操作。
  3. 标准的原型模式需要对象实现 Cloneable, Serializable 两个接口。
  4. 建议的方式:对象实现 Serializable 接口(不用实现Cloneable接口),提供克隆工具类: PrototypeUtils,调用者使用PrototypeUtils进行属性复制,由决定浅复制还是深复制。

PrototypeUtils工具类代码

import com.alibaba.fastjson.JSON;
import org.springframework.beans.BeanUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * @description: 原型模式工具类
 * @author: chujian
 * @since: 2022-02-19 16:23
 **/
@SuppressWarnings("unchecked")
public class PrototypeUtils {
    /**
     * 浅克隆
     *
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> T clone(T obj) {
        if (null == obj) return null;
        Object newInstance = null;
        try {
            newInstance = obj.getClass().getConstructor().newInstance();
            BeanUtils.copyProperties(obj, newInstance);
            return (T) newInstance;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 深度克隆
     *
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> T deepClone(T obj) {
        if (null == obj) return null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            Object o = ois.readObject();
            return (T) o;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 深度克隆,通过Json序列化的方式
     *
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> T deepCloneByJson(T obj) {
        String jsonString = JSON.toJSONString(obj);
        Object object = JSON.parseObject(jsonString, obj.getClass());
        return (T) object;
    }
}

本文完整源码地址

码云代码地址

举报

相关推荐

0 条评论