首頁 > 軟體

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

2020-11-02 09:00:27

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]

IT145.com E-mail:sddin#qq.com