Android的設計模式-原型模式

前言

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類圖

原型模式UML類圖.jpg

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}}
----------------------
說明:
  1. clone對象時不會執行構造函數
  2. 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 淺拷貝

上面的例子實際上就是一個淺拷貝,如下圖所示:


淺拷貝.png

由于num是基本數據類型,因此直接將整數值拷貝過來就行。但是spec是Spec類型的, 它只是一個引用,指向一個真正的Spec對象,那么對它的拷貝有兩種方式:

  1. 直接將源對象中的spec的引用值拷貝給新對象的spec字段,這種拷貝方式就叫淺拷貝。

5.2 深拷貝

另外一種拷貝方式就是根據原Card對象中的spec指向的對象創建一個新的相同的對象,將這個新對象的引用賦給新拷貝的Card對象的spec字段。這種拷貝方式就叫深拷貝,如下圖所示:

深拷貝.png

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的設計模式-橋接模式

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容