「補課」進行時:設計模式(6)——郭靖大俠帶你學原型模式

1. 前文彙總

「補課」進行時:設計模式系列

2. 找工作

這一天,郭靖大俠因爲在桃花島調戲侍女被黃蓉打出了桃花島,這下可玩大了,從桃花島被趕出來喫啥喝啥啊,得趕緊找份工作,西北風可喝不飽肚子哇~~~

這不,我們的郭大俠就開始寫簡歷,準備向丐幫、全真教、白駝山和段氏家族投一份簡歷,看看能不能先混碗飯喫,等老婆的氣消了再回去。

首先,先定義一個簡歷類:

public class Resume {
    private String name;
    private String position;
    private int salary;

    // 省略 get/set

    @Override
    public String toString() {
        return "Resume{" +
                "name='" + name + '\'' +
                ", position='" + position + '\'' +
                ", salary=" + salary +
                '}';
    }
}

然後,我們的郭大俠開始了熬夜寫簡歷的生活:

public class Test {
    public static void main(String[] args) {
        Resume resume1 = new Resume();
        resume1.setName("小郭");
        resume1.setPosition("一代大俠");
        resume1.setSalary(1000);
        System.out.println(resume1);

        Resume resume2 = new Resume();
        resume2.setName("小郭");
        resume2.setPosition("一代大俠");
        resume2.setSalary(1200);
        System.out.println(resume2);

        Resume resume3 = new Resume();
        resume3.setName("小郭");
        resume3.setPosition("一代大俠");
        resume3.setSalary(1500);
        System.out.println(resume3);

        // ...
}

簡歷這麼一份一份的寫太累了,工作都沒找到可能先餓死了,不行,小郭同學需要提高寫簡歷的效率,於是,他去找了一個打印機回來:

public class Test {
    public static void main(String[] args) {
        // 效率倍增,直接循環開始寫簡歷
        for (int i = 0; i < 5; i++) {
            Resume resume4 = new Resume();
            int salary = (int)(1000 + Math.random() * (2000 - 1000 + 1));
            resume4.setName("小郭");
            resume4.setPosition("一代大俠");
            resume4.setSalary(salary);
            System.out.println(resume4.toString());
        }
    }
}

這個時候,感覺效率好像還是有點低,每次只能一張一張打印,浪費時間,於是乎,我們的郭大俠又去搞了一個複印機回來。

可是使用複印機需要我們原本的簡歷支持這個功能,聽過這個功能需要擴展 Cloneable 接口:

public class ResumeClone implements Cloneable {
    private String name;
    private String position;
    private int salary;
    // 省略 get/set
    @Override
    protected ResumeClone clone(){
        ResumeClone resumeClone = null;
        try{
            resumeClone = (ResumeClone) super.clone();
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return resumeClone;
    }

    @Override
    public String toString() {
        return "ResumeClone{" +
                "name='" + name + '\'' +
                ", position='" + position + '\'' +
                ", salary=" + salary +
                '}';
    }
}

然後我們的複印機就能跑起來了:

public class TestClone {
    public static void main(String[] args) {
        int num = 5;
        ResumeClone resumeClone = new ResumeClone();
        while (num > 0){
            ResumeClone resume1 = resumeClone.clone();
            int salary = (int)(1000 + Math.random() * (2000 - 1000 + 1));
            resume1.setName("小郭");
            resume1.setPosition("一代大俠");
            resume1.setSalary(salary);
            System.out.println(resume1.toString());
            num --;
        }
    }
}

這裏實際上我們只有第一個對象是使用打印機打印出來的,後面的對象都是通過複印機直接複印出來的。

這其實就是設計模式中的原型模式。

3. 原型模式

原型模式(Prototype Pattern)的簡單程度僅次於單例模式和迭代器模式。正是由於簡單,使用的場景才非常地多,其定義如下:

Specify the kinds of objects to create using a prototypical instance,andcreate new objects by copying this prototype.(用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。)

這個絕對是最簡單的設計模式,整個模式的核心就只有一個 clone 方法,通過該方法進行對象的拷貝, Java 提供了一個 Cloneable 接口來標示這個對象是可拷貝的,爲什麼說是「標示」呢?翻開 JDK 的幫助看看 Cloneable 是一個方法都沒有的,這個接口只是一個標記作用,在 JVM 中具有這個標記的對象纔有可能被拷貝。那怎麼才能從「有可能被拷貝」轉換爲「可以被拷貝」呢?方法是覆蓋 clone() 方法。

通用代碼:

public class PrototypeClass implements Cloneable{
    @Override
    protected PrototypeClass clone() {
        PrototypeClass prototypeClass = null;
        try {
            prototypeClass = (PrototypeClass) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return prototypeClass;
    }
}

優點:

  1. 性能優良

原型模式是在內存二進制流的拷貝,要比直接 new 一個對象性能好很多,特別是要在一個循環體內產生大量的對象時,原型模式可以更好地體現其優點。

  1. 逃避構造函數的約束

這既是它的優點也是缺點,直接在內存中拷貝,構造函數是不會執行的。優點就是減少了約束,缺點也是減少了約束。

4. 構造函數

先看一個簡單的有關構造函數的示例:

public class ConstructorDemo implements Cloneable {
    public ConstructorDemo() {
        System.out.println("我被執行了。。。");
    }

    @Override
    protected ConstructorDemo clone(){
        ConstructorDemo demo = null;
        try {
            demo = (ConstructorDemo) super.clone();
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return demo;
    }
}

public class ConstructorTest {
    public static void main(String[] args) {
        ConstructorDemo demo = new ConstructorDemo();
        ConstructorDemo demo1 = demo.clone();
    }
}

執行結果如下:

我被執行了。。。

就輸出一次,這裏可以證明對象拷貝的時候構造函數是不會執行的,原因在於拷貝是直接在堆中進行,這其實也可以理解, new 的時候, JVM 要走一趟類加載流程,這個流程非常麻煩,在類加載流程中會調用構造函數,最後生成的對象會放到堆中,而拷貝就是直接拷貝堆中的現成的二進制對象,然後重新一個分配內存塊。

5. 淺拷貝和深拷貝

先看一個淺拷貝的案例:

public class ShallowCopy implements Cloneable {
    private ArrayList<String> array = new ArrayList<> ();
    @Override
    public ShallowCopy clone() {
        ShallowCopy copy = null;
        try {
            copy = (ShallowCopy) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return copy;
    }

    public void setValue(String value) {
        this.array.add(value);
    }

    public ArrayList<String> getValue() {
        return this.array;
    }
}

public class ShallowCopyTest {
    public static void main(String[] args) {
        ShallowCopy copy = new ShallowCopy();
        copy.setValue("123");
        ShallowCopy copy1 = copy.clone();
        copy1.setValue("456");
        System.out.println(copy.getValue());
    }
}

執行的結果是:

[123, 456]

這種情況就是淺拷貝, Java 只拷貝你指定的對象,至於你指定的對象裏面的別的對象,它不拷貝,還是把引用給你,共享變量,這是一種非常不安全的方式,需要特別注意。

內部的數組和引用對象不會拷貝,其他的原始基本類型和 String 類型會被拷貝。

那麼這種情況如何進行一個深拷貝呢?只需要修改一下剛纔 clone 的方法:

// 深拷貝
@Override
public ShallowCopy clone() {
    ShallowCopy copy = null;
    try {
        copy = (ShallowCopy) super.clone();
        this.array = (ArrayList<String>) this.array.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return copy;
}

還是剛纔的測試類,這次的運行結果是:

[123]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章