1. 程式人生 > >設計模式--原型模式(用於物件的複製)

設計模式--原型模式(用於物件的複製)

目錄

1、淺拷貝

一、new一個物件的步驟

1、分配記憶體大小;

2、呼叫建構函式填充(初始化);

3、把物件的引用釋出,別的地方可以引用

二、new一個物件和複製一個物件(clone)對比

clone顧名思義就是複製, 在Java語言中, clone方法被物件呼叫,所以會複製物件。所謂的複製物件,首先要分配一個和源物件同樣大小的空間,在這個空間中建立一個新的物件。那麼在java語言中,有幾種方式可以建立物件呢?

1 使用new操作符建立一個物件
2 使用clone方法複製一個物件

那麼這兩種方式有什麼相同和不同呢? new操作符的本意是分配記憶體。程式執行到new操作符時, 首先去看new操作符後面的型別,因為知道了型別,才能知道要分配多大的記憶體空間。分配完記憶體之後,再呼叫建構函式,填充物件的各個域,這一步叫做物件的初始化,構造方法返回後,一個物件建立完畢,可以把他的引用(地址)釋出到外部,在外部就可以使用這個引用操縱這個物件。
而clone在第一步是和new相似的, 都是分配記憶體,呼叫clone方法時,分配的記憶體和源物件(即呼叫clone方法的物件)相同,然後再使用原物件中對應的各個域,填充新物件的域, 填充完成之後,clone方法返回,一個新的相同的物件被建立,同樣可以把這個新物件的引用釋出到外部。

通過上面的描述可知,new一個物件和clone一個物件的區別在第2步,即一個是呼叫建構函式完成初始化,而另外一個是用原物件的各個域填充新的記憶體空間完成初始化

三、應用:原型模式主要用於物件的複製(clone)

很簡單,一個原型類,只需要實現Cloneable介面,覆寫clone方法,此處clone方法可以改成任意的名稱,因為Cloneable介面是個空介面,你可以任意定義實現類的方法名,如cloneA或者cloneB,因為此處的重點是super.clone()這句話,super.clone()呼叫的是Object的clone()方法,而在Object類中,clone()是native的。作用是返回物件的一個拷貝,但是其作用域protected型別的,一般的類無法呼叫,因此,Prototype類需要將clone方法的作用域修改為public型別。

1、物件複製分類

1)、淺複製

將一個物件複製後,基本資料型別的變數都會重新建立,而引用型別,指向的還是原物件所指向的。

class Prototype implements Cloneable {
     public Prototype clone(){
         Prototype prototype = null;
        try{
             prototype = (Prototype)super.clone();
         }catch(CloneNotSupportedException e){
             e.printStackTrace();
         }
         return prototype;
     }
 }

2)、深複製

將一個物件複製後,不論是基本資料型別還有引用型別,都是重新建立的。簡單來說,就是深複製進行了完全徹底的複製,而淺複製不徹底。

2、原型模式的優點

  • 使用原型模式建立物件比直接new一個物件在效能上要好的多,因為Object類的clone方法是一個本地方法,它直接操作記憶體中的二進位制流,特別是複製大物件時,效能的差別非常明顯。
  • 使用原型模式的另一個好處是簡化物件的建立,使得建立物件就像我們在編輯文件時的複製貼上一樣簡單。

   因為以上優點,所以在需要重複地建立相似物件時可以考慮使用原型模式。比如需要在一個迴圈體內建立物件,假如物件建立過程比較複雜或者迴圈次數很多的話,使用原型模式不但可以簡化建立過程,而且可以使系統的整體效能提高很多。

3、原型模式的注意事項

1)、使用原型模式複製物件不會呼叫類的構造方法。

因為物件的複製是通過呼叫Object類的clone方法來完成的,它直接在記憶體中複製資料,因此不會呼叫到類的構造方法。不但構造方法中的程式碼不會執行,甚至連訪問許可權都對原型模式無效。單例模式中,只要將構造方法的訪問許可權設定為private型,就可以實現單例。但是clone方法直接無視構造方法的許可權,所以,單例模式與原型模式是衝突的,在使用時要特別注意。

2)深拷貝與淺拷貝

Object類的clone方法只會拷貝物件中的基本的資料型別(8種基本資料型別byte,char,short,int,long,float,double,boolean),對於陣列、容器物件、引用物件等都不會拷貝,這就是淺拷貝。如果要實現深拷貝,必須將原型模式中的陣列、容器物件、引用物件等另行拷貝。例如:

 public class Prototype implements Cloneable {
     private ArrayList list = new ArrayList();
     public Prototype clone(){
         Prototype prototype = null;
         try{
             prototype = (Prototype)super.clone();
             prototype.list = (ArrayList) this.list.clone();
         }catch(CloneNotSupportedException e){
             e.printStackTrace();
         }
         return prototype;
     }
 }

由於ArrayList不是基本型別,所以成員變數list,不會被拷貝,需要我們自己實現深拷貝,幸運的是Java提供的大部分的容器類都實現了Cloneable介面。所以實現深拷貝並不是特別困難。

四、測試淺拷貝和深度拷貝

1、淺拷貝

public class Prototype implements Cloneable {
    ArrayList<Integer> list = new ArrayList<Integer>();

    public Prototype() {
        list.add(1);
        list.add(2);
    }
    public static void main(String[] args) {
        Prototype p = new Prototype();
        System.out.println(p);
        System.out.println(p.list);
        Prototype p1 = p.clone();
        p.list.add(3);
        System.out.println(p.list);

        System.out.println(p1);
        System.out.println(p1.list);
    }

  //淺拷貝
    public Prototype clone() {
        Prototype prototype = null;
        try {
            prototype = (Prototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return prototype;
    }
}
//淺拷貝的輸出結果
[email protected]
[1, 2]
[1, 2, 3]
[email protected]
[1, 2, 3]

從結果可以看出,p和p1指向同一個list,因此,當對其中的一個修改的時候,另外一個也會同步變化

2、深度拷貝

public class Prototype implements Cloneable {
    ArrayList<Integer> list = new ArrayList<Integer>();

    public Prototype() {
        list.add(1);
        list.add(2);
    }
    public static void main(String[] args) {
        Prototype p = new Prototype();
        System.out.println(p);
        System.out.println(p.list);
        Prototype p1 = p.clone();
        p.list.add(3);
        System.out.println(p.list);

        System.out.println(p1);
        System.out.println(p1.list);
    }

 //深拷貝
    public Prototype clone() {
        Prototype prototype = null;
        try {
            prototype = (Prototype) super.clone();
            prototype.list = (ArrayList)this.list.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return prototype;
    }

}
//深拷貝的輸出結果
[email protected]
[1, 2]
[1, 2, 3]
[email protected]
[1, 2]

從結果看出,p和p1分別指向不同的list

參考:

《head first 設計模式》