設計模式干貨系列:(五)原型模式【學習難度:★★★☆☆,使用頻率:★★★☆☆】

前言

今天介紹原型模式,我自己偷偷給它命名為克隆模式。因為原型模式的意圖是通過復制一個現有的對象來生成新的對象,而不是通過實例化的方式。

正文

原型模式概念

原型模式(Prototype Pattern):使用原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象。原型模式是一種對象創建型模式。

該接口用于創建當前對象的克隆。當直接創建對象的代價比較大時,則采用這種模式。例如,一個對象需要在一個高代價的數據庫操作之后被創建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新數據庫,以此來減少數據庫調用。

需要注意的是通過克隆方法所創建的對象是全新的對象,它們在內存中擁有新的地址,通常對克隆所產生的對象進行修改對原型對象不會造成任何影響,每一個克隆對象都是相互獨立的。通過不同的方式修改可以得到一系列相似但不完全相同的對象。

原型模式結構圖


在原型模式結構圖中包含如下幾個角色:

  • 抽象原型角色(Prototype):它是聲明克隆方法的接口,是所有具體原型類的公共父類,可以是抽象類也可以是接口,甚至還可以是具體實現類。
  • 具體原型角色(ConcretePrototype):它實現在抽象原型類中聲明的克隆方法,在克隆方法中返回自己的一個克隆對象。
  • 客戶角色(Client):客戶類提出創建對象的請求。

原型模式的核心在于如何實現克隆方法,博主本人是從事Java開發,所以用Java語言提供的clone()方法來實現。學過Java語言的人都知道,所有的Java類都繼承自java.lang.Object。事實上,Object類提供一個clone()方法,可以將一個Java對象復制一份。因此在Java中可以直接使用Object提供的clone()方法來實現對象的克隆,Java語言中的原型模式實現很簡單。

需要注意的是能夠實現克隆的Java類必須實現一個標識接口Cloneable,表示這個Java類支持被復制。如果一個類沒有實現這個接口但是調用了clone()方法,Java編譯器將拋出一個CloneNotSupportedException異常。

克隆滿足的條件
clone()方法將對象復制了一份并返還給調用者。所謂“復制”的含義與clone()方法是怎么實現的有關。一般而言,clone()方法滿足以下的描述:

  • 對任何的對象x,都有:x.clone()!=x 。換言之,克隆對象與元對象不是一個對象。
  • 對任何的對象x,都有:x.clone().getClass==x.getClass(),換言之,克隆對象與元對象的類型一樣。
  • 如果對象x的equals()方法定于恰當的話,那么x.clone().equals(x)應當是成立的。
    在Java語言的API中,凡是提供了clone()方法的類,都滿足上面的這些條件。Java語言的設計師在設計自己的clone()方法時,也應當遵守這三個條件。

在理解Java原型模式之前,首先需要理解Java中的一個概念:復制/克隆。Java中的對象復制/克隆分為淺復制和深復制。在Java語言中,數據類型分為值類型(基本數據類型)和引用類型,值類型包括intdoublebytebooleanchar等簡單數據類型,引用類型包括類、接口、數組等復雜類型。淺克隆和深克隆的主要區別在于是否支持引用類型的成員變量的復制,下面將對兩者進行詳細介紹。

淺克隆

在淺克隆中,如果原型對象的成員變量是值類型,將復制一份給克隆對象;如果原型對象的成員變量是引用類型,則將引用對象的地址復制一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內存地址。簡單來說,在淺克隆中,當對象被復制時只復制它本身和其中包含的值類型的成員變量,而引用類型的成員對象并沒有復制。
下面以復制一本書為例:
下面是Author源代碼:

public class Author {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

下面是Book源代碼:

public class Book  implements Cloneable{
    private String bookName;
    private int price;
    private Author author;

    public Book clone() {
        Book  book=null;
        try {
            book=(Book)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return book;

    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

下面是客戶端類:

public class Client {
    public static void main(String[] args){
        Author author=new Author();
        author.setName("tengj");
        Book book=new Book();
        book.setBookName("Java設計模式");
        book.setPrice(99);
        book.setAuthor(author);
        Book book2=book.clone();
        System.out.println(book==book2);                                 // false
        System.out.println(book.getBookName() == book2.getBookName());   // true
        System.out.println(book.getAuthor() == book2.getAuthor());       // true
    }
}

由輸出的結果可以驗證說到的結論。由此我們發現:雖然復制出來的對象重新在堆上開辟了內存空間,但是,對象中各屬性確保持相等。對于基本數據類型很好理解,但對于引用數據類型來說,則意味著此引用類型的屬性所指向的對象本身是相同的, 并沒有重新開辟內存空間存儲。換句話說,引用類型的屬性所指向的對象并沒有復制。
由此,我們將其稱之為淺復制。當復制后的對象的引用類型的屬性所指向的對象也重新得以復制,此時,稱之為深復制。

深克隆

在深克隆中,無論原型對象的成員變量是值類型還是引用類型,都將復制一份給克隆對象,深克隆將原型對象的所有引用對象也復制一份給克隆對象。簡單來說,在深克隆中,除了對象本身被復制外,對象所包含的所有成員變量也將復制。

在Java語言中,如果需要實現深克隆,可以通過序列化(Serialization)等方式來實現。序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在于內存中。通過序列化實現的拷貝不僅可以復制對象本身,而且可以復制其引用的成員對象,因此通過序列化將對象寫到一個流中,再從流里將其讀出來,可以實現深克隆。需要注意的是能夠實現序列化的對象其類必須實現Serializable接口,否則無法實現序列化操作。

還是以復制一本書為例:
下面是Author源代碼(Author也需要實現Serializable接口!!):

public class Author implements Serializable{
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

下面是Book源代碼(Book類需要實現Serializable接口):

public class Book  implements Serializable{
    private String bookName;
    private int price;
    private Author author;

    public Book deepClone() throws IOException, ClassNotFoundException {
// 寫入當前對象的二進制流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        // 讀出二進制流產生的新對象
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Book) ois.readObject();
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

下面是客戶端類:

public class DeepClient {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Author author=new Author();
        author.setName("tengj");
        Book book=new Book();
        book.setBookName("Java設計模式");
        book.setPrice(99);
        book.setAuthor(author);
        Book book2=book.deepClone();
        System.out.println(book==book2);                                 // false
        System.out.println(book.getBookName() == book2.getBookName());   // false
        System.out.println(book.getAuthor() == book2.getAuthor());       // false
    }
}

從輸出結果中可以看出,深復制不僅在堆內存上開辟了空間以存儲復制出的對象,甚至連對象中的引用類型的屬性所指向的對象也得以復制,重新開辟了堆空間存儲。

總結

原型模式作為一種快速創建大量相同或相似對象的方式,在軟件開發中應用較為廣泛,很多軟件提供的復制(Ctrl + C)和粘貼(Ctrl + V)操作就是原型模式的典型應用,下面對該模式的使用效果和適用情況進行簡單的總結。
1.主要優點
原型模式的主要優點如下:

  • 當創建新的對象實例較為復雜時,使用原型模式可以簡化對象的創建過程,通過復制一個已有實例可以提高新實例的創建效率。
  • 擴展性較好,由于在原型模式中提供了抽象原型類,在客戶端可以針對抽象原型類進行編程,而將具體原型類寫在配置文件中,增加或減少產品類對原有系統都沒有任何影響。
  • 原型模式提供了簡化的創建結構,工廠方法模式常常需要有一個與產品類等級結構相同的工廠等級結構,而原型模式就不需要這樣,原型模式中產品的復制是通過封裝在原型類中的克隆方法實現的,無須專門的工廠類來創建產品。
  • 可以使用深克隆的方式保存對象的狀態,使用原型模式將對象復制一份并將其狀態保存起來,以便在需要的時候使用(如恢復到某一歷史狀態),可輔助實現撤銷操作。

2.主要缺點
原型模式的主要缺點如下:

  • 需要為每一個類配備一個克隆方法,而且該克隆方法位于一個類的內部,當對已有的類進行改造時,需要修改源代碼,違背了“開閉原則”。
  • 在實現深克隆時需要編寫較為復雜的代碼,而且當對象之間存在多重的嵌套引用時,為了實現深克隆,每一層對象對應的類都必須支持深克隆,實現起來可能會比較麻煩。

3.適用場景
在以下情況下可以考慮使用原型模式:

  • 創建新對象成本較大(如初始化需要占用較長的時間,占用太多的CPU資源或網絡資源),新的對象可以通過原型模式對已有對象進行復制來獲得,如果是相似對象,則可以對其成員變量稍作修改。
  • 如果系統要保存對象的狀態,而對象的狀態變化很小,或者對象本身占用內存較少時,可以使用原型模式配合備忘錄模式來實現。
  • 需要避免使用分層次的工廠類來創建分層次的對象,并且類的實例對象只有一個或很少的幾個組合狀態,通過復制原型對象得到新實例可能比使用構造函數創建一個新實例更加方便。

一直覺得自己寫的不是技術,而是情懷,一篇篇文章是自己這一路走來的痕跡。靠專業技能的成功是最具可復制性的,希望我的這條路能讓你少走彎路,希望我能幫你抹去知識的蒙塵,希望我能幫你理清知識的脈絡,希望未來技術之巔上有你也有我。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容