1. 程式人生 > >淺談prototype原型模式

淺談prototype原型模式

一、原型模式簡介

原型(Prototype)模式是一種物件建立型模式,他採取複製原型物件的方法來建立物件的例項。使用原型模式建立的例項,具有與原型一樣的資料。

原型模式的特點:

1、由原型物件自身建立目標物件。也就是說,物件建立這一動作發自原型物件本身。

2、目標物件是原型物件的一個克隆。也就是說,通過原型模式建立的物件,不僅僅與原型物件具有相同的結構,還與原型物件具有相同的值。

3、根據物件克隆深度層次的不同,有淺度克隆與深度克隆。

1、寫一個支援克隆的類

//如果要克隆就必須實現Cloneable介面
public class Person implements Cloneable{
    //可能會丟擲不支援克隆異常,原因是沒有實現Cloneable介面
    @Override
    protected Person clone(){
        try{
            return (Person) super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
            return null;
        }
    }
}

2、克隆類

public class MainClass {
    public static void main(String[] args) {
        Person person1 = new Person();
        Person person2 = person1.clone();
    }
}

這樣子克隆並不等同於Person p2 = p1;像Person p2 = p1;指的是在棧中建立一個變數p2,將p1的記憶體地址賦給p2,其實指的是同一個物件。而克隆是複製出一份一模一樣的物件,兩個物件記憶體地址不同,但物件中的結構與屬性值一模一樣。

這種不通過 new 關鍵字來產生一個物件,而是通過物件拷貝來實現的模式就叫做原型模式,這個模式的核心是一個clone( )方法,通過這個方法進行物件的拷貝,Java 提供了一個 Cloneable 介面來標示這個物件是可拷貝的,為什麼說是“標示”呢?翻開 JDK 的幫助看看 Cloneable 是一個方法都沒有的,這個介面只是一個標記作用,在 JVM 中具有這個標記的物件才有可能被拷貝,所以覆蓋了覆蓋clone()方法就可以了。

在 clone()方法上增加了一個註解@Override, 沒有繼承一個類為什麼可以重寫呢?在 Java 中所有類的父類是Object 類,每個類預設都是繼承了這個類,所以這個用上@Override是非常正確的。原型模式雖然很簡單,但是在 Java 中使用原型模式也就是 clone 方法還是有一些注意事項的:物件拷貝時,類的建構函式是不會被執行的。

 一個實現了 Cloneable 並重寫了 clone 方法的類 A,有一個無參構造或有參構造 B,通過 new 關鍵字產生了一個物件 S,再然後通過 S.clone()方式產生了一個新的物件 T,那麼在物件拷貝時建構函式 B 是不會被執行的, 物件拷貝時確實建構函式沒有被執行,這個從原理來講也是可以講得通的,Object 類的 clone 方法的 原理是從記憶體中(具體的說就是堆記憶體)以二進位制流的方式進行拷貝,重新分配一個記憶體塊,那建構函式 沒有被執行也是非常正常的了。

二、深度克隆與淺度克隆

1、淺度克隆

//如果要克隆就必須實現Cloneable介面
public class Person implements Cloneable{
    private String name;
    private String sex;
    private List<String> list;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public List<String> getList() {
        return list;
    }
    public void setList(List<String> list) {
        this.list = list;
    }
    //可能會丟擲不支援克隆異常,原因是沒有實現Cloneable介面
    @Override
    protected Person clone(){
        try{
            return (Person) super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
            return null;
        }
    }
}

這就是淺度克隆,當被克隆的類中有引用物件(String或Integer等包裝型別除外)時,克隆出來的類中的引用變數儲存的還是之前的記憶體地址,也就是說克隆與被克隆的物件是同一個。這樣的話兩個物件共享了一個私有變數,所有人都可以改,是一個種非常不安全的方式,在實際專案中使用還是比較少的。

2、深度拷貝

//如果要克隆就必須實現Cloneable介面
public class Person implements Cloneable{
    private String name;
    private String sex;
    private List<String> list;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public List<String> getList() {
        return list;
    }
    public void setList(List<String> list) {
        this.list = list;
    }
    //可能會丟擲不支援克隆異常,原因是沒有實現Cloneable介面
    @Override
    protected Person clone(){
        try{
            Person person = (Person) super.clone();
            List<String> newList = new ArrayList();
            
            for(String str : this.list){
                newList.add(str);
            }
            person.setList(newList);
            return person;
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
            return null;
        }
    }
}

這樣就完成了深度拷貝,兩種物件互為獨立,屬於單獨物件。

注意:final 型別修飾的成員變數不能進行深度拷貝

三、原型模式的使用場景  

  1. 在建立物件的時候,我們不只是希望被建立的物件繼承其基類的基本結構,還希望繼承原型物件的資料。
  2. 希望對目標物件的修改不影響既有的原型物件(深度克隆的時候可以完全互不影響)。
  3. 隱藏克隆操作的細節,很多時候,對物件本身的克隆需要涉及到類本身的資料細節。
  4. 類初始化需要消耗非常多的資源,這個資源包括資料、硬體資源等。
  5. 通過new產生一個物件需要非常繁瑣的資料準備或訪問許可權,則可以使用原型模式;
  6. 一個物件需要提供給其它物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用。在實際專案中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過clone的方法建立一個物件,然後由工廠方法提供給呼叫者。原型模式先產生一個包含大量共有資訊的類,然後可以拷貝出副本,修正細節訊息,建立了一個完整的