設計模式--原型模式(用於物件的複製)
目錄
一、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 設計模式》