设计模式之原型模式


代码优雅之路-设计模式之原型模式

概述

在日常开发过程当中,我们难免会遇到类与类之间转换的过程,可能这两个类字段类型,名称只存在微小区别,甚至一模一样,比如DTO到Entity过程,这个时候就需要本文的重点模式-原型模式

概念

原型模式(Prototype Pattern) 官方原文为:

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

意思大概是当我们使用原型实例要指定创建对象种类的时候,通过复制该原型创建新对象。

核心

原型模式的核心是需要拷贝原型对象,且不能通过new对象的形式getter,setter来进行拷贝,需将原型对象通过内存二进制流的形式进行拷贝,二进制流的形式无需经历new对象的构造过程,性能会些许提升。

应用场景

原型模式适用(包含但不限于)以下场景

  1. 体力劳动过剩
  2. 类初始化消耗资源过多
  3. 构造方法过于复杂

看到这,可能会问,体力劳动过剩是什么鬼? 大家看一篇代码示例:

public static void main(String[] args) {
    OldDomain old = new OldDomain();
    ...省略set过程
        
    NewDomain newDomain = new NewDomain();
    newDomain.setAddress(old.getAddress());
    newDomain.setAge(old.getAge());
    newDomain.setName(old.getName());
    newDomain.setIdCard(old.getIdCard());
    newDomain.setPhone(old.getPhone());
}

疯不疯? 况且真实项目情况时,可能会有几十个,甚至嵌套了N个模型类吧,一个一个去getter,setter,即使你体力劳动过剩也不能这么玩吧。但是这种方式不能完全否定,为什么呢,文末说明。

使用

JDK为我们提供了一个原型接口Cloneable:

public interface Cloneable {
}

让我们的原型对象去实现它并重写clone()方法;

public class NewDomain implements Cloneable {
    /** 名字 */
    private String name;
    /** 年龄 */
    private String age;
    /** 地址 */
    private String address;
    /** 电话 */
    private String phone;
    /** 身份证 */
    private String idCard;
    @Override
    protected NewDomain clone() throws CloneNotSupportedException {
        return (NewDomain) super.clone();
    }
}

我们创建原型对象并进行克隆操作:

public static void main(String[] args) throws CloneNotSupportedException {
    NewDomain newDomain = new NewDomain();
    newDomain.setAddress("中国");
    newDomain.setAge("18");
    newDomain.setName("法外狂徒张三");
    newDomain.setIdCard("112233");
    newDomain.setPhone("1100");
    NewDomain cloneNewDomain = newDomain.clone();
    System.out.println("第一个对象---------" + newDomain.getName());
    System.out.println("克隆对象----------" + cloneNewDomain.getName());
}

我们可以得到如下输出

第一个对象---------法外狂徒张三
克隆对象----------法外狂徒张三

至此,我们发现已经克隆成功,但是这种形式只是浅克隆形式,也就是内存地址引用,原型发生变化,克隆对象也会跟着变化,如:

public static void main(String[] args) throws CloneNotSupportedException {
    NewDomain newDomain = new NewDomain();
    newDomain.setAddress("中国");
    newDomain.setName("法外狂徒张三");
    newDomain.setCars(new ArrayList<>(Arrays.asList("自行车","三轮车")));
    NewDomain cloneNewDomain = newDomain.clone();
    System.out.println("第一个对象---------" + newDomain);
    System.out.println("克隆对象----------" + cloneNewDomain);
    newDomain.getCars().add("电动车");
    System.out.println("第一个对象赋值后---------" + newDomain);
    System.out.println("克隆对象----------" + cloneNewDomain);
}

输出结果如下

第一个对象---------[自行车, 三轮车]
克隆对象----------[自行车, 三轮车]
第一个对象赋值后---------[自行车, 三轮车, 电动车]
克隆对象----------[自行车, 三轮车, 电动车]

你会发现,第一个对象发生改变后,被克隆对象也发生了改变,如果你的新对象不想受原型对象影响,就需要采用深克隆形式,深克隆形式有两种一种是JSON序列化,另一种则是流读取输出的形式,现在已二进制流读取

public NewDomain deepClone() throws IOException, ClassNotFoundException {
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
    objectOutputStream.writeObject(this);

    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
    return (NewDomain) objectInputStream.readObject();
}

将上面的newDomain.clone();变更为newDomain.deepClone(); 执行结果如下:

第一个对象---------[自行车, 三轮车]
克隆对象----------[自行车, 三轮车]
第一个对象赋值后---------[自行车, 三轮车, 电动车]
克隆对象----------[自行车, 三轮车]

你会发现克隆对象不再受原型对象的改变而改变。

结言

原型模式的优点就是性能还不错,毕竟是基于二进制流,比直接new对象性能上要提升许多,缺点也很明显,如果想实现深克隆,势必要每个类都需书写深克隆方法,在对象嵌套过程当中,实现过程势必繁琐。但是我们在工作当中,深克隆的方式避免不了,而且我们在工作中往往不去在每个对象当中去书写深克隆方法,为了方便,我们可能通过反射来获取,但这些深克隆方式势必会影响性能,相比之下,getter,setter的效率反而比较高。所以原型模式为我们提供了一种理念,我们可以根据实际需求去运用。


文章作者: TimeRoar
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 TimeRoar !
评论
  目录