[設計模式] 創建型:原型模式(Prototype Pattern)

什麼是原型模式

聽說過克隆技術吧?再通俗點,就是複製。

原型模式就是代碼裏面的克隆技術,把一個已經實例化的對象當作原型,克隆出一個跟它相同的對象。

可以採用new方式構造一個新對象,再把原型對象的內部狀態拷貝過來,達到複製原型對象的目的,但是這就涉及關心到了原型內部,沒有克隆模式來的簡單直接。

原型模式的實現思路

從是否具備克隆能力的角度看,有兩種實現思路:

  1. 原型自身具備克隆能力,就像孫悟空,拔根猴毛就能變出一個分身
  2. 原型自身不具備克隆能力,就像克隆羊多利,是生物學家掌握克隆技術,從母體身上克隆出來的

Java代碼實現角度看,有三種實現方案:

  1. 利用Object類提供的clone方法實現,有淺克隆、深克隆兩種模式
  2. 利用對象序列化機制實現,只有深克隆模式
  3. 還有一種方法就當發散思維了,那就是直接new新對象,再將原型模式屬性都複製過來(實際不這麼幹,也不推薦)

clone()

因爲Object類裏面提供有clone方法,而Object類又是所有類的隱形基類,所以,通過繼承的途徑,每個對象就天然擁有自己克隆自己的潛力。

但是這個clone()只能做到淺克隆模式,要做到深克隆還需要自己動手多做點事情。

序列化

序列化就是把內存中的對象信息存儲起來,需要的時候,再通過反序列化將儲存信息轉化成內存中的對象信息。

這句話有兩個要點:

  1. 序列化是轉換內存對象信息的過程,不包括對象的內存地址信息
  2. 反序列化是根據對象信息重建內存對象的過程

因此,這種方法只能實現深克隆模式,不能實現淺克隆模式。

原型模式的實現代碼

淺克隆

淺克隆的意思是:克隆出來的對象,所有“基本類型變量值”和“引用類型變量值”都和原型對象一樣。

也就是說,如果原型對象中有一個屬性指向對象A,那麼克隆對象相同的屬性也指向對象A

淺克隆只複製原型對象、以及原型對象屬性的基本類型值和引用值,不會複製內部屬性引用的其它對象。

代碼實現分兩步走:

  1. 原型類實現java.lang.Cloneable接口,這是一個空的標記接口,並無實際作用,只是遵循Java的編程規範,表明這類對象可以克隆。
  2. 原型類重寫Object類中的clone方法,並將其訪問修飾符變更爲public,重寫邏輯裏面需要調用Objectclone方法,也就是super.clone()

代碼實現:

public class Teacher {
}

// 原型
public class Student implements Cloneable {
    private Teacher teacher;

    public Student(Teacher teacher) {
        this.teacher = teacher;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    @Override
    public Student clone() throws CloneNotSupportedException {
        return (Student) super.clone();
    } 
}

// 測試
public static void main(String[] args) throws CloneNotSupportedException {
    Student student = new Student(new Teacher());// 原型對象
    Student clone = student.clone();// 克隆對象
    System.out.println(student.getTeacher() == clone.getTeacher());// true
}

深克隆

深克隆的意思是:克隆出來的對象,所有“基本類型變量值”和原型對象相同,“引用類型變量值”和原型對象不同。

也就是說,如果原型對象中有一個屬性指向對象A,那麼,對象A也要被克隆,最後,克隆對象相同的屬性不是指向A對象,而是指向新對象A'

深克隆不僅複製原型對象,還會複製原型對象內部屬性的引用對象,克隆的特別透徹。

實現深克隆,就在淺克隆基礎上多做一點事情:手動把原型所有的引用屬性再克隆一次。

前面說了,深克隆有兩種方法:clone ()、序列化。

第一種clone(),代碼實現:

public class Teacher implements Cloneable {
    @Override
    protected Teacher clone() throws CloneNotSupportedException {
        return (Teacher) super.clone();
    }
}

// 原型
public class Student implements Cloneable {
    private Teacher teacher;

    public Student(Teacher teacher) {
        this.teacher = teacher;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    @Override
    public Student clone() throws CloneNotSupportedException {
        Student clone = (Student) super.clone();
        clone.teacher = teacher.clone(); // 手動賦值,深克隆需要自己編碼多做事情
        return clone;
    }
}

// 測試
public static void main(String[] args) throws CloneNotSupportedException {
    Student student = new Student(new Teacher());// 原型對象
    Student clone = student.clone();// 克隆對象
    System.out.println(student.getTeacher() == clone.getTeacher());// false
}

第二種序列化,需要注意一點,通過這種方式進行的深克隆,相關類都需要實現java.io.Serializable接口,這是Java序列化機制要求的。代碼實現:

public class Teacher implements Serializable {
}

// 原型
public class Student implements Serializable {
    private Teacher teacher;

    public Student(Teacher teacher) {
        this.teacher = teacher;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    // 因爲沒有利用Object的clone方法,所以,最好也別重寫clone方法,新定義一個方法比較好
    public Student deepClone() throws IOException, ClassNotFoundException {
        //將對象寫到流裏
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        //從流裏讀回來
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Student) ois.readObject();
    }
}

// 測試
public static void main(String[] args) throws IOException, ClassNotFoundException {
    Student student = new Student(new Teacher());
    Student clone = student.deepClone();
    System.out.println(student.getTeacher() == clone.getTeacher());
}

原型模式的優缺點

優點

一旦構建好原型模式的克隆邏輯,就可以很方便的得到一個和原型一摸一樣的對象,不需要關心原型的內部信息。

缺點

需要理解淺克隆和深克隆的含義,不能在這上面犯錯。

克隆邏輯往往具有全局影響力,設計的時候需要全盤考量,對原來的克隆邏輯進行改動更是要慎之又慎!

當對象引用屬性包含着循環結構的時候,特別容易出問題!

採用序列化機制的時候,還要考慮對象引用屬性是否都能繼承Serializable接口,如果屬性對象是第三方jar包裏的,很可能就會出現不支持序列化的問題,要慎重!

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