• 1. 界说
  • 2. 适用场景
  • 3. 分类
    • 3.1 浅克隆实现
  • 5. Cloneable 源码剖析

1. 界说

指原型实例指定建立工具的种类,而且通过拷贝这些原型建立新的工具

挪用者不需要知道任何建立细节,不挪用组织函数

2. 适用场景

  • 类初始化消耗资源较多
  • new 发生的一个工具需要异常繁琐的历程(数据准备,接见权限等)
  • 组织函数比较复杂
  • 循环体重生产大量工具时
  • Spring 中的 scope 就是使用的原型模式

2.1 痛点问题场景

你一定遇到过大篇幅 getter,setter 赋值的场景.例如这样的代码

public void setParam(ExamPaperVo vo){
    ExamPaper examPaper = new ExamPaper();
    //试卷主键
    examPaper.setExaminationPaperId(vo.getExaminationPaperId());
    //剩余时间
    curForm.setLeavTime(examPaper.getLeavTime());
    //单元主键
    curForm.setOrganizationId(examPaper.getOrganizationId());
    //考试主键
    curForm.setId(examPaper.getId());
    //科场主键
    curForm.setExamroomId(examPaper.getExamroomId());
    //用户主键
    curForm.setUserId(examPaper.getUserId());
    //专业
    curForm.setSpecialtyCode(examPaper.getSpecialtyCode());
    //岗位
    curForm.setPostionCode(examPaper.getPostionCode());
    //品级
    curForm.setGradeCode(examPaper.getGradeCode());
    //考试最先时间
    curForm.setExamStartTime(examPaper.getExamStartTime());
    //考试竣事时间
    curForm.setExamEndTime(examPaper.getExamEndTime());
    //单选题主要数目
    curForm.setSingleSelectionImpCount(examPaper.getSingleSelectionImpCount());
    //多选题主要数目
    curForm.setMultiSelectionImpCount(examPaper.getMultiSelectionImpCount());
    //判断题主要数目
    curForm.setJudgementImpCount(examPaper.getJudgementImpCount());
    //考试时间
    curForm.setExamTime(examPaper.getExamTime());
    //总分
    curForm.setFullScore(examPaper.getFullScore());
    //及格分
    curForm.setPassScore(examPaper.getPassScore());
    //学员姓名
    curForm.setUserName(examPaper.getUserName());
    //分数
    curForm.setScore(examPaper.getScore());
    //是否及格
    curForm.setResult(examPaper.getResult());
    curForm.setIsPassed(examPaper.getIsPassed());
    //单选答对数目
    curForm.setSingleOkCount(examPaper.getSingleOkCount());
    //多选答对数目
    curForm.setMultiOkCount(examPaper.getMultiOkCount());
    //判断答对数目
    curForm.setJudgementOkCount(examPaper.getJudgementOkCount());
    //提交试卷
    service.submit(examPaper);
}

代码异常工致,命名异常规范,注释也写的很周全,人人以为这样的代码优雅吗?我以为,这样的代码属于纯体力劳动.那么原型模式,能辅助我们解决这样的问题

3. 分类

  • 浅克隆:指拷贝工具时仅仅 copy 工具自己和工具中的基本变量,而不拷贝工具包罗的引用指向的工具
  • 深克隆:不仅 copy 工具自己,而且 copy 工具包罗的引用指向的所有工具

3.1 浅克隆实现

  • 原型 prototype 接口

    public interface Prototype {
    Prototype clone();
    }
  • 需要克隆类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class ConcretePrototypeA implements Prototype {
    /**
    * 岁数
    */
    private int age;
    /**
    * 姓名
    */
    private String name;
    /**
    * 兴趣爱好
    */
    private List hobbies;
    @Override
    public Prototype clone() {
    return new ConcretePrototypeA(this.age, this.name, this.hobbies);
    }
    }
  • 测试类

    package com.zhunongyun.toalibaba.designpatterns.prototype.simelp;
    

import java.util.ArrayList;

public class PrototypeTest {

,

皇冠下载

(www.huangguan.us)是一个提供皇冠代理APP下载、皇冠会员APP下载、皇冠体育最新登录线路、新2皇冠网址的的体育平台。也只有皇冠APP可以真正地带给你顶级体育赛事的娱乐体验感。立马一键皇冠体育开户,世界体育赛事等你欣赏。

,
public static void main(String[] args) {
    ConcretePrototypeA concretePrototypeA = new ConcretePrototypeA(18, "test_name", new ArrayList());

    ConcretePrototypeA copy = (ConcretePrototypeA) concretePrototypeA.clone();

    System.out.println("原工具与克隆工具内存地址对比, 地址是否相同:"
            + (concretePrototypeA == copy));

    System.out.println("原工具与克隆工具中引用类型内存地址对比, 地址是否相同:"
            + (concretePrototypeA.getHobbies() == copy.getHobbies()));
}

}


运行效果
![](https://huaweirookie.oss-cn-shenzhen.aliyuncs.com/20200715213356.jpg)

从测试效果看出 hobbies 的引用地址是相同的,意味着复制的不是值,而是引用的地址.这样的话,若是我们修改随便一个工具中的属性值,concretePrototype 和 concretePrototypeCone 的 hobbies 值都市改变.这就是我们常说的浅克隆.只是完整复制了值类型数据,没有赋值引用工具

* 浅克隆对于基本数据类型和 String 类型字段会重新申请内存复制数据,克隆工具会指向新的内存地址
* 浅克隆对于引用类型的字段不会重新申请内存,而是把字段的内存地址指向之前原工具字段的内存地址

## 3.2 深克隆
界说一个孙悟空类,拔一根毫毛吹出万万个猴子,每个猴子都有属于自己的金箍棒

通过字节码实现深克隆

* 待克隆类
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JinGuBang implements Serializable {

    private float height = 100;

    private float wide = 10;

    public void big() {
        this.height *= 2;
        this.wide *= 2;
    }

    public void  *** all() {
        this.height /= 2;
        this.wide /= 2;
    }
}
@Data
public class QiTianDaSheng implements Cloneable, Serializable {

    private JinGuBang jinGuBang;

    private int height;

    private int weight;

    private Date birthday;

    public QiTianDaSheng() {
        this.birthday = new Date();
        this.jinGuBang = new JinGuBang();
    }

    @Override
    protected Object clone() {
        return this.deepClone();
    }

    public Object deepClone() {
        ByteArrayOutputStream byteArrayOutputStream = null;
        ObjectOutputStream objectOutputStream = null;

        ByteArrayInputStream byteArrayInputStream = null;
        ObjectInputStream objectInputStream = null;

        // 内存中完成操作,工具读写,是通过字节码直接操作
        // 序列化操作类似
        try {
            byteArrayOutputStream = new ByteArrayOutputStream();
            objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(this);

            byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            objectInputStream = new ObjectInputStream(byteArrayInputStream);

            // 完整的新的工具, new 出来一个新的工具
            QiTianDaSheng copy = (QiTianDaSheng) objectInputStream.readObject();
            copy.setBirthday(new Date());

            return copy;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            IOUtils.closeQuietly(objectInputStream);
            IOUtils.closeQuietly(byteArrayInputStream);
            IOUtils.closeQuietly(objectOutputStream);
            IOUtils.closeQuietly(byteArrayOutputStream);
        }
    }
}
  • 测试类
    ```java
    package com.zhunongyun.toalibaba.designpatterns.prototype.deep;

public class DeepCloneTest {
public static void main(String[] args) {
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();

    QiTianDaSheng clone = (QiTianDaSheng) qiTianDaSheng.clone();

    System.out.println("深克隆,工具中的引用类型字段 JinGuBang,内存地址是否相同:"
            + (qiTianDaSheng.getJinGuBang() == clone.getJinGuBang()));
}

}


运行效果
![b4d128ce939829eeeff142cf1514531f.png](evernotecid://0B7710CE-D342-4510-A5F9-81F7B62D33F2/wwwevernotecom/149352153/ENResource/p155)

# 4. 克隆损坏单例模式
若是我们克隆的目的的工具是单例工具,那意味着,深克隆就会损坏单例.实际上防止克隆损坏单例解决思绪异常简朴,克制深克隆便可.要么你我们的单例类不实现 Cloneable 接口;要么我们重写 clone()方式,在 clone 方式中返回单例工具即可,详细代码如下
```java
@Override
protected Object clone() throws CloneNotSupportedException {
    return INSTANCE;
}

5. Cloneable 源码剖析

Cloneable 是符号型的接口,它们内部都没有方式和属性,实现 Cloneable 来示意该工具能被克隆,能使用 Object.clone()方式

若是没有实现 Cloneable 的类工具挪用 clone()就会抛出 CloneNotSupportedException

在 ArrayList 中存在 clone 方式,但属于浅克隆

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}