您现在的位置是:首页 >技术教程 >设计模式之原型模式网站首页技术教程

设计模式之原型模式

King Gigi. 2024-06-14 17:17:37
简介设计模式之原型模式

假设一个场景,例如我们现在要写周报,现在我们发现周报里面需要填很多信息,例如姓名、部门、职位等等,这些信息基本都不会变,但是每次填周报我们又需要填一遍,这样显然不合理,我们其实想的是,写周报只需要填汇报内容即可

那应该怎么办呢?是不是希望能够保存一下上一次编辑的模板,下次直接修改内容就可以了,我们用一个类来表示一下

@Data // 自动生成getter setter 和 toString方法
@Accessors(chain = true) // 开启链式编程
class WeekReport{
    private int id;
    private String emp;
    private String summary;
    private String plain;
    private String suggestion;
    private String department;
    private LocalDateTime submitDate;
}
public class App {
    public static void main(String[] args) {
        WeekReport weekReport = new WeekReport();
        weekReport.setEmp("King")
                .setSummary("本周主要完成了七大设计原则")
                .setPlain("在下周的刷完面试题")
                .setDepartment("互联网事业部")
                .setSubmitDate(LocalDateTime.now());
        // 简单输出一下
        System.out.println(weekReport);
        // 第二周周报
        WeekReport weekReport2 = new WeekReport();
        weekReport2.setEmp("King")
                .setSummary("本周主要完成了设计模式的学习")
                .setPlain("在下周会搞懂30道算法")
                .setDepartment("互联网事业部")
                .setSubmitDate(LocalDateTime.now());
        System.out.println(weekReport2);
    }
}

我们会发现其实下一周要进行汇报时,我们只需要修改总结和下周计划的内容,但是现在我们却只能再新建一个对象重复上述代码

我们想要什么,想要的是直接克隆出一个对象来,将需要改变的填一下,不变的用之前的就好

现在通过克隆来实现上面的需求,在Java中只需要实现Cloneable,并重写克隆方法即可

@Data // 自动生成getter setter 和 toString方法
@Accessors(chain = true) // 开启链式编程
class WeekReport implements Cloneable {
    private int id;
    private String emp;
    private String summary;
    private String plain;
    private String suggestion;
    private String department;
    private LocalDateTime submitDate;
    //重写方法后将修饰符改为public
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class App {
    public static void main(String[] args) throws CloneNotSupportedException {
        WeekReport weekReport = new WeekReport();
        weekReport.setEmp("King")
                .setSummary("本周主要完成了七大设计原则")
                .setPlain("在下周的刷完面试题")
                .setDepartment("互联网事业部")
                .setSubmitDate(LocalDateTime.now());
        // 简单输出一下
        System.out.println(weekReport);
        // 第二周周报
        WeekReport weekReport2 = (WeekReport) weekReport.clone();
        weekReport2.setSummary("本周主要完成了设计模式的学习")
                .setPlain("在下周会搞懂30道算法")
                .setSubmitDate(LocalDateTime.now());
        System.out.println(weekReport2);
    }
}

这就是原型设计模式,这里还有几个点要注意一下

  • 上面的克隆其实只是浅拷贝

  • 使用clone方法创建对象,并不会调用该对象的构造器

    因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

这里再聊一下深拷贝浅拷贝的概念

类型特点
浅拷贝被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

说人话

  • 浅拷贝:浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是指向内存的地址 ,所以如果其中一个对象改变了这个引用类型的值,就会影响到另一个对象。浅拷贝使用默认的clone()方法来实现。
  • 深拷贝:深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

看下面的栗子:可以把setSubmitDate看做一个指针,弄了一个新对象给它,会在堆空间里面新申请一块内存,当然就不一样了

在这里插入图片描述

而weekReport2改变了引用类型的值,就会影响到weekReport对象

在这里插入图片描述

那我们如何深拷贝呢?其实也很简单,既然原来的Object.clone()方法无法克隆引用类型,那我们自己来克隆就好了

其中大部分类其实都是实现了clone方法的

@Override
protected Object clone() throws CloneNotSupportedException {
    WeekReport cloneWeekReport = (WeekReport) super.clone();
    // Date类型需要自己手动进行克隆
    Date cloneSubmitDate = (Date) cloneWeekReport.getSubmitDate().clone();
    cloneWeekReport.setSubmitDate(cloneSubmitDate);
    return cloneWeekReport;
}

但是这样很麻烦,如果我们克隆的是自己定义的对象,或者是对象里面套对象,这样层层嵌套的形式,就会非常麻烦

封装深拷贝工具

所以我们一般是用序列化和反序列化来做的,并且会封装成一个工具,这个拷贝在开发中还是非常常见的,例如我们经常将一个PO转换为一个VO(就是拷贝对象),一般用Spring自带的BeanUtils,但是它只能浅拷贝,我们现在自己封装一个,可以选择继承Spring自带的BeanUtils,或者就放deepClone这一个方法就行

/**
 * 拷贝Bean
 */
@Slf4j
public final class BeanUtil extends BeanUtils {

    private BeanUtil() {
        throw new UnsupportedOperationException();
    }

    /**
     * 拷贝属性
     */
    public static <T> T copyProperties(Object source, Class<T> targetClass) {
        if (checkNull(source, targetClass)) {
            return null;
        }
        try {
            T newInstance = targetClass.newInstance();
            copyProperties(source, newInstance);
            return newInstance;
        } catch (Exception e) {
            log.error("error: ", e);
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T deepClone(T object) {
        T cloneObject = null;
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(object);
            objectOutputStream.close();
            ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            cloneObject = (T) objectInputStream.readObject();
            objectInputStream.close();
        } catch (ClassNotFoundException | IOException e) {
            log.info("拷贝异常::", e);
        }
        return cloneObject;
    }

    public static <T> T copyProperties(Object source, Class<T> targetClass, String... ignoreProperties) {
        if (checkNull(source, targetClass)) {
            return null;
        }
        try {
            T newInstance = targetClass.newInstance();
            copyProperties(source, newInstance, ignoreProperties);
            return newInstance;
        } catch (Exception e) {
            log.error("error: ", e);
            return null;
        }
    }

    /**
     * 拷贝集合
     */
    public static <T> List<T> copyProperties(List<?> sources, Class<T> targetClass) {
        if (checkNull(sources, targetClass) || sources.isEmpty()) {
            return new ArrayList<>();
        }
        return sources.stream().map(source -> copyProperties(source, targetClass)).collect(Collectors.toList());
    }

    private static <T> boolean checkNull(Object source, Class<T> targetClass) {
        return Objects.isNull(source) || Objects.isNull(targetClass);
    }

}

现在在来看看原来的拷贝,这里需要注意进行序列化的对象需要实现Serializable接口

@Data // 自动生成getter setter 和 toString方法
@Accessors(chain = true) // 开启链式编程
class WeekReport implements Serializable {
    private int id;
    private String emp;
    private String summary;
    private String plain;
    private String suggestion;
    private String department;
    private Date submitDate;
}
public class App {
    public static void main(String[] args) throws CloneNotSupportedException {
        WeekReport weekReport = new WeekReport();
        weekReport.setEmp("King")
                .setSummary("本周主要完成了七大设计原则")
                .setPlain("在下周的刷完面试题")
                .setDepartment("互联网事业部")
                .setSubmitDate(new Date());
        // 简单输出一下
        System.out.println("weekReport的时间为:"+ weekReport.getSubmitDate());

        // 第二周周报  深拷贝
        WeekReport weekReport2 = BeanUtil.deepClone(weekReport);
        weekReport2.setSummary("本周主要完成了设计模式的学习")
                .setPlain("在下周会搞懂30道算法");

        // 这样才是修改同一个引用指向堆空间里面的值
        weekReport2.getSubmitDate().setTime(0);
        System.out.println(weekReport == weekReport2);
        System.out.println("weekReport2的时间为:"+ weekReport2.getSubmitDate());
        System.out.println("weekReport的时间为:"+ weekReport.getSubmitDate());

    }
}

在这里插入图片描述

可以看到修改新对象不会影响到原来的对象。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。