前言
Android的設計模式系列文章介紹,歡迎關注,持續更新中:
Android的設計模式-設計模式的六大原則
一句話總結23種設計模式則
創建型模式:
Android的設計模式-單例模式
Android的設計模式-建造者模式
Android的設計模式-工廠方法模式
Android的設計模式-簡單工廠模式
Android的設計模式-抽象工廠模式
Android的設計模式-原型模式
行為型模式:
Android的設計模式-策略模式
Android的設計模式-狀態模式
Android的設計模式-責任鏈模式
Android的設計模式-觀察者模式
Android的設計模式-模板方法模式
Android的設計模式-迭代器模式
Android的設計模式-備忘錄模式
Android的設計模式-訪問者模式
Android的設計模式-中介者模式
Android的設計模式-解釋器模式
Android的設計模式-命令模式
結構型模式:
Android的設計模式-代理模式
Android的設計模式-組合模式
Android的設計模式-適配器模式
Android的設計模式-裝飾者模式
Android的設計模式-享元模式
Android的設計模式-外觀模式
Android的設計模式-橋接模式
1.定義
用原型實例指定創建對象的種類,并通過拷貝這些原型創建新的對象。
2.介紹
- 原型模式屬于創建型模式。
- 一個已存在的對象(即原型),通過復制原型的方式來創建一個內部屬性跟原型都一樣的新的對象,這就是原型模式。
- 原型模式的核心是clone方法,通過clone方法來實現對象的拷貝。
3.UML類圖
3.1 角色說明:
- Prototype(抽象原型類):抽象類或者接口,用來聲明clone方法。
- ConcretePrototype1、ConcretePrototype2(具體原型類):即要被復制的對象。
- Client(客戶端類):即要使用原型模式的地方。
4.實現
4.1 Prototype(抽象原型類)
- 通常情況下,Prototype是不需要我們去定義的。因為拷貝這個操作十分常用,Java中提供了Cloneable接口來支持拷貝操作,它就是原型模式中的Prototype。
- 當然,原型模式也未必非得去實現Cloneable接口,也有其他的實現方式。
4.2 創建具體原型類
實現Cloneable接口:
//具體原型類,卡片類
public class Card implements Cloneable {//實現Cloneable接口,Cloneable只是標識接口
private int num;//卡號
private Spec spec = new Spec();//卡規格
public Card() {
System.out.println("Card 執行構造函數");
}
public void setNum(int num) {
this.num = num;
}
public void setSpec(int length, int width) {
spec.setLength(length);
spec.setWidth(width);
}
@Override
public String toString() {
return "Card{" +
"num=" + num +
", spec=" + spec +
'}';
}
@Override
protected Card clone() throws CloneNotSupportedException {//重寫clone()方法,clone()方法不是Cloneable接口里面的,而是Object里面的
System.out.println("clone時不執行構造函數");
return (Card) super.clone();
}
}
//規格類,有長和寬這兩個屬性
public class Spec {
private int width;
private int length;
public void setLength(int length) {
this.length = length;
}
public void setWidth(int width) {
this.width = width;
}
@Override
public String toString() {
return "Spec{" +
"width=" + width +
", length=" + length +
'}';
}
}
4.3 創建客戶端類
即要使用原型模式的地方:
public class Client {
public void test() throws CloneNotSupportedException {
Card card1 = new Card();
card1.setNum(9527);
card1.setSpec(10, 20);
System.out.println(card1.toString());
System.out.println("----------------------");
Card card2 = card1.clone();
System.out.println(card2.toString());
System.out.println("----------------------");
}
}
輸出結果:
Card 執行構造函數
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
clone時不執行構造函數
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
說明:
- clone對象時不會執行構造函數
- clone方法不是Cloneable接口中的,而是Object中的方法。Cloneable是個標識接口,表面了這個對象是可以拷貝的,如果沒有實現Cloneable接口卻調用clone方法則會報錯。
但是,如果執行下面的代碼:
Card card1 = new Card();
card1.setNum(9527);
card1.setSpec(10, 20);
System.out.println(card1.toString());
System.out.println("----------------------");
Card card2 = card1.clone();
System.out.println(card2.toString());
System.out.println("----------------------");
card2.setNum(7259);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");
card2.setSpec(30, 40);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");
其輸出結果為:
Card 執行構造函數
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
clone時不執行構造函數
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
Card{num=9527, spec=Spec{width=20, length=10}}
Card{num=7259, spec=Spec{width=20, length=10}}
----------------------
Card{num=9527, spec=Spec{width=40, length=30}}
Card{num=7259, spec=Spec{width=40, length=30}}
----------------------
我們會發現,修改了拷貝對象的引用類型(即Spec字段)時,原來的對象的值也跟著改變了;但是修改基本對象(num字段)時,原來的對象的值卻不會改變。這就涉及到深拷貝和淺拷貝了。
5. 深拷貝和淺拷貝
5.1 淺拷貝
上面的例子實際上就是一個淺拷貝,如下圖所示:
由于num是基本數據類型,因此直接將整數值拷貝過來就行。但是spec是Spec類型的, 它只是一個引用,指向一個真正的Spec對象,那么對它的拷貝有兩種方式:
- 直接將源對象中的spec的引用值拷貝給新對象的spec字段,這種拷貝方式就叫淺拷貝。
5.2 深拷貝
另外一種拷貝方式就是根據原Card對象中的spec指向的對象創建一個新的相同的對象,將這個新對象的引用賦給新拷貝的Card對象的spec字段。這種拷貝方式就叫深拷貝,如下圖所示:
5.3 原型模式改造
那么,我們對上面原型模式的例子進行改造,使其實現深拷貝,這就需要在Card的clone方法中,將源對象引用的Spec對象也clone一份。
public static class Card implements Cloneable {
private int num;
private Spec spec = new Spec();
public Card() {
System.out.println("Card 執行構造函數");
}
public void setNum(int num) {
this.num = num;
}
public void setSpec(int length, int width) {
spec.setLength(length);
spec.setWidth(width);
}
@Override
public String toString() {
return "Card{" +
"num=" + num +
", spec=" + spec +
'}';
}
@Override
protected Card clone() throws CloneNotSupportedException {
System.out.println("clone時不執行構造函數");
Card card = (Card) super.clone();
card.spec = (Spec) spec.clone();//對spec對象也調用clone,實現深拷貝
return card;
}
}
public static class Spec implements Cloneable {//Spec也實現Cloneable接口
private int width;
private int length;
public void setLength(int length) {
this.length = length;
}
public void setWidth(int width) {
this.width = width;
}
@Override
public String toString() {
return "Spec{" +
"width=" + width +
", length=" + length +
'}';
}
@Override
protected Spec clone() throws CloneNotSupportedException {//重寫Spec的clone方法
return (Spec) super.clone();
}
}
測試代碼:
Card card1 = new Card();
card1.setNum(9527);
card1.setSpec(10, 20);
System.out.println(card1.toString());
System.out.println("----------------------");
Card card2 = card1.clone();
System.out.println(card2.toString());
System.out.println("----------------------");
card2.setNum(7259);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");
card2.setSpec(30, 40);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");
其輸出結果為:
Card 執行構造函數
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
clone時不執行構造函數
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
Card{num=9527, spec=Spec{width=20, length=10}}
Card{num=7259, spec=Spec{width=20, length=10}}
----------------------
Card{num=9527, spec=Spec{width=20, length=10}}
Card{num=7259, spec=Spec{width=40, length=30}}
----------------------
由此可見,card1和card2內的spec引用指向了不同的Spec對象, 也就是說在clone Card對象的同時,也拷貝了它所引用的Spec對象, 進行了深拷貝。
6. 應用場景
- 如果初始化一個類時需要耗費較多的資源,比如數據、硬件等等,可以使用原型拷貝來避免這些消耗。
- 通過new創建一個新對象時如果需要非常繁瑣的數據準備或者訪問權限,那么也可以使用原型模式。
- 一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以拷貝多個對象供調用者使用,即保護性拷貝。
7. 優點
- 可以解決復雜對象創建時消耗過多的問題,在某些場景下提升創建對象的效率。
- 保護性拷貝,可以防止外部調用者對對象的修改,保證這個對象是只讀的。
8. 缺點
- 拷貝對象時不會執行構造函數。
- 有時需要考慮深拷貝和淺拷貝的問題。
9. Android中的源碼分析
Android中的Intent就實現了Cloneable接口,但是clone()方法中卻是通過new來創建對象的。
public class Intent implements Parcelable, Cloneable {
//其他代碼略
@Override
public Object clone() {
return new Intent(this);//這里沒有調用super.clone()來實現拷貝,而是直接通過new來創建
}
public Intent(Intent o) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
this.mFlags = o.mFlags;
this.mContentUserHint = o.mContentUserHint;
if (o.mCategories != null) {
this.mCategories = new ArraySet<String>(o.mCategories);
}
if (o.mExtras != null) {
this.mExtras = new Bundle(o.mExtras);
}
if (o.mSourceBounds != null) {
this.mSourceBounds = new Rect(o.mSourceBounds);
}
if (o.mSelector != null) {
this.mSelector = new Intent(o.mSelector);
}
if (o.mClipData != null) {
this.mClipData = new ClipData(o.mClipData);
}
}
}
總結 :
- 實際上,調用clone()構造對象時并不一定比new快,使用clone()還是new來創建對象需要根據構造對象的成本來決定,如果對象的構造成本比較高或者構造比較麻煩,那么使用clone()的效率比較高,否則使用new。
相關文章閱讀
Android的設計模式-設計模式的六大原則
一句話總結23種設計模式則
創建型模式:
Android的設計模式-單例模式
Android的設計模式-建造者模式
Android的設計模式-工廠方法模式
Android的設計模式-簡單工廠模式
Android的設計模式-抽象工廠模式
Android的設計模式-原型模式
行為型模式:
Android的設計模式-策略模式
Android的設計模式-狀態模式
Android的設計模式-責任鏈模式
Android的設計模式-觀察者模式
Android的設計模式-模板方法模式
Android的設計模式-迭代器模式
Android的設計模式-備忘錄模式
Android的設計模式-訪問者模式
Android的設計模式-中介者模式
Android的設計模式-解釋器模式
Android的設計模式-命令模式
結構型模式:
Android的設計模式-代理模式
Android的設計模式-組合模式
Android的設計模式-適配器模式
Android的設計模式-裝飾者模式
Android的設計模式-享元模式
Android的設計模式-外觀模式
Android的設計模式-橋接模式