【创建模式-原型模式(Prototype Pattern)】

夜雨寄北

唐.李商隐
君问归期未有期,巴山夜雨涨秋池。

何当共剪西窗烛,却话巴山夜雨时。

目的

需要创建某个类时,以某个类的某个具体的、典型的实例作为原型实例;通过拷贝复制该原型实例来创建新的对象。大白话就是不通过new关键字来产生一个对象,而是通过对象复制来创建对象。

动机

降低通过new关键字创建对象的开销;这里的开销主要源于构造函数内部的构建过程

应用场景

当需要反复构建出同一个类的大量实例,并且这些实例的初始状态都是一样的时候就可以先通过new创建出一个实例,对该实例进行调整(通常就是设置一些参数),这个实例就可以称之为原型,这也是该设计模式叫原型模式的缘由。

我们可以举一个实际的例子(引用自:《设计模式之禅道》),银行批量向客户发送节日邮件,假设一封邮件包括:①称呼、②节日祝福语;那么操作流程应该是这样的:

创建一个模板,对该模板设置节日祝福语,因为所有的人节日祝福语是相同的;

为每一个客户依次克隆一个第一步的模板(如果不新建模板的话,多线程并发时是不安全的,通过new创建效率不如克隆),得到一个实例,将该实例传递给一个发送邮件线程;

发送线程对传递得到的邮件实例,进行称呼设置,然后进行邮件发送;

注意事项

  • 原型模式的构造函数不会被执行,Object类的clone方法的原理是从内存中以二进制流的方式进行拷贝,重新分配一块内存块,所以构造函数不被执行;
  • 深拷贝和浅拷贝,jdk自带的克隆函数是浅拷贝,如果需要深拷贝需要程序员自己实现
  • final修饰的属性和clone冲突,在编译阶段就会报错
  • 要使用clone方法,类成员变量上不要增加final关键字

性能评测

java 复制代码
/**
 * 原型模式 VS 构造函数 性能测试
 *
 * @author hipilee
 */
public class JdkCloneable implements Cloneable {
    public int properties1 = 0;

    public JdkCloneable() {
    }

    public JdkCloneable(int properties1) {
        HashMap<Integer, Integer> hashMap = new HashMap(100);
        for (int i = 0; i < 10; i++) {
            hashMap.put(properties1, properties1);
        }

    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        long start, end;
        start = System.currentTimeMillis();
        JdkCloneable jdkCloneable = new JdkCloneable(1000);
        for (int i = 0; i < 10000000; i++) {
            jdkCloneable.clone();
        }
        end = System.currentTimeMillis();
        System.out.println("原型模式:耗时" + (end - start) / 1000.0 + "毫秒。");

        start = System.currentTimeMillis();
        for (int j = 0; j < 10000000; j++) {
            new JdkCloneable();
        }
        end = System.currentTimeMillis();
        System.out.println("默认构造:耗时" + (end - start) / 1000.0 + "毫秒。");

        start = System.currentTimeMillis();
        for (int j = 0; j < 10000000; j++) {
            new JdkCloneable(j);
        }
        end = System.currentTimeMillis();
        System.out.println("复杂构造:耗时" + (end - start) / 1000.0 + "毫秒。");
    }
}

输出:

原型模式:耗时0.357毫秒。

简单构造:耗时0.008毫秒。

复杂构造:耗时6.016毫秒。

由此我们可以得出,原型模式相对于new关键字构造对象的优势在于:如果new构造调用的构造函数比较耗时时,原型模式才有明显优势;因为原型模式是从内存中拷贝二进制数据不会调用构造函数。

更优实践

通过使用Hutool工具类,来解决jdk自身的Cloneable接口不方便的地方:①java.lang.Cloneable接口是个空接口起标记作用,在对类进行克隆时,如果该类只是实现了java.lang.Cloneable接口并没有重写Object的clone()函数,那么会抛出CloneNotSupportedException异常;

②Object的clone()函数返回的对象是Object,克隆后需要程序员自己强转下类型;

③jdk的克隆函数是浅拷贝

泛型克隆接口

实现Hutool的cn.hutool.core.clone.Cloneable接口。此接口定义了一个返回泛型的成员方法,这样,实现此接口后会提示必须实现一个public的clone方法,调用父类clone方法即可:

java 复制代码
import cn.hutool.core.clone.CloneRuntimeException;
import cn.hutool.core.clone.Cloneable;

/**
 * 猫猫类,使用实现Cloneable方式
 * @author Looly
 *
 */
class Cat implements Cloneable<Cat>{
    private String name = "miaomiao";
    private int age = 2;

    @Override
    public Cat clone() {
        try {
            return (Cat) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new CloneRuntimeException(e);
        }
    }
}

泛型克隆类

实现cn.hutool.core.clone.Cloneable接口依旧有不方便之处,就是必须自己实现一个public类型的clone()方法,还要调用父类(Object)的clone方法并处理异常。于是cn.hutool.clone.CloneSupport类产生,这个类帮我们实现了上面的clone方法,因此只要继承此类,不用写任何代码即可使用clone()方法:

java 复制代码
import cn.hutool.core.clone.CloneSupport;

/**
 * 狗狗类,用于继承CloneSupport类
 * @author Looly
 *
 */
public class Dog extends CloneSupport<Dog> {
    private String name = "wangwang";
    private int age = 3;
}

深克隆

我们知道实现Cloneable接口后克隆的对象是浅克隆,要想实现深克隆,请使用:

java 复制代码
ObjectUtil.cloneByStream(obj)

前提是对象必须实现Serializable接口。

相关推荐
道友老李10 小时前
【设计模式精讲】创建型模式之原型模式(深克隆、浅克隆)
设计模式·原型模式
強云2 天前
23种设计模式 - 原型模式
设计模式·原型模式
le_duoduo3 天前
原型模式详解(Java)
java·原型模式
lonelyhiker3 天前
javascript的原型链
开发语言·javascript·原型模式
新时代的弩力4 天前
Function.prototype.__proto__==Object.prototype
开发语言·javascript·原型模式
无限大.6 天前
前端知识速记—JS篇:原型与原型链
前端·javascript·原型模式
wy02_7 天前
【设计模式】 建造者模式和原型模式
设计模式·建造者模式·原型模式
ox00808 天前
C++ 设计模式-原型模式
c++·设计模式·原型模式
码了三年又三年10 天前
面向对象中的原型
原型模式