[01][01][05] 原型模式詳解

1. 定義

指原型實例指定建立對象的種類,而且經過拷貝這些原型建立新的對象java

調用者不須要知道任何建立細節,不調用構造函數app

2. 適用場景

  • 類初始化消耗資源較多
  • new 產生的一個對象須要很是繁瑣的過程(數據準備,訪問權限等)
  • 構造函數比較複雜
  • 循環體重生產大量對象時
  • Spring 中的 scope 就是使用的原型模式

2.1 痛點問題場景

你必定遇到過大篇幅 getter,setter 賦值的場景.例如這樣的代碼ide

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);
    }
    }
  • 測試類ui

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

import java.util.ArrayList;this

public class PrototypeTest {prototype

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()));
}

}code

運行結果
![](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 small() {
        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);
    }
}
相關文章
相關標籤/搜索