原型模式
原型模式解决的是大量属性复制问题。
 本文将介绍几种深度属性复制方式、几种浅度属性复制方式。
 本文源码地址将在文末给出。
未使用原型模式之前的代码
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;
        }
    }
}
特别交代
- 并非所有原型模式都需要深度复制,看实际的业务场景。
- 原型模式的本意是较少手动复制大量属性的愚蠢操作。
- 标准的原型模式需要对象实现 Cloneable, Serializable 两个接口。
- 建议的方式:对象实现 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;
    }
}
本文完整源码地址
码云代码地址









