創建型SEQ5 - 原型模式-Prototype Pattern

【學習難度:★★★☆☆,使用頻率:★★★☆☆】
直接出處:原型模式
梳理和學習:https://github.com/BruceOuyang/boy-design-pattern
簡書日期: 2018/03/07
簡書首頁:http://www.lxweimin.com/p/0fb891a7c5ed

對象的克隆——原型模式(一)

張紀中版《西游記》以出乎意料的造型和雷人的臺詞遭到廣大觀眾朋友的熱議,我們在此對該話題不作過多討論。但無論是哪個版本的《西游記》,孫悟空都是其中的一號雄性主角,關于他(或它)拔毛變小猴的故事幾乎人人皆知,孫悟空可以用猴毛根據自己的形象,復制(又稱“克隆”或“拷貝”)出很多跟自己長得一模一樣的“身外身”來。

在設計模式中也存在一個類似的模式,可以通過一個原型對象克隆出多個一模一樣的對象,該模式稱之為原型模式。

7.1 大同小異的工作周報

Sunny軟件公司一直使用自行開發的一套OA (Office Automatic,辦公自動化)系統進行日常工作辦理,但在使用過程中,越來越多的人對工作周報的創建和編寫模塊產生了抱怨。追其原因,Sunny軟件公司的OA管理員發現,由于某些崗位每周工作存在重復性,工作周報內容都大同小異,如圖7-1工作周報示意圖。這些周報只有一些小地方存在差異,但是現行系統每周默認創建的周報都是空白報表,用戶只能通過重新輸入或不斷復制粘貼來填寫重復的周報內容,極大降低了工作效率,浪費寶貴的時間。如何快速創建相同或者相似的工作周報,成為Sunny公司OA開發人員面臨的一個新問題。

圖7-1 工作周報示意圖

Sunny公司的開發人員通過對問題進行仔細分析,決定按照如下思路對工作周報模塊進行重新設計和實現:

(1)除了允許用戶創建新周報外,還允許用戶將創建好的周報保存為模板;

(2)用戶在再次創建周報時,可以創建全新的周報,還可以選擇合適的模板復制生成一份相同的周報,然后對新生成的周報根據實際情況進行修改,產生新的周報。

只要按照如上兩個步驟進行處理,工作周報的創建效率將得以大大提高。這個過程讓我們想到平時經常進行的兩個電腦基本操作:復制和粘貼,快捷鍵通常為Ctrl + C和Ctrl + V,通過對已有對象的復制和粘貼,我們可以創建大量的相同對象。

如何在一個面向對象系統中實現對象的復制和粘貼呢?不用著急,本章我們介紹的原型模式正為解決此類問題而誕生。

7.2 原型模式概述

在使用原型模式時,我們需要首先創建一個原型對象,再通過復制這個原型對象來創建更多同類型的對象。試想,如果連孫悟空的模樣都不知道,怎么拔毛變小猴子呢?原型模式的定義如下:原型模式(Prototype Pattern):使用原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象。原型模式是一種對象創建型模式。

原型模式的工作原理很簡單:將一個原型對象傳給那個要發動創建的對象,這個要發動創建的對象通過請求原型對象拷貝自己來實現創建過程。由于在軟件系統中我們經常會遇到需要創建多個相同或者相似對象的情況,因此原型模式在真實開發中的使用頻率還是非常高的。原型模式是一種“另類”的創建型模式,創建克隆對象的工廠就是原型類自身,工廠方法由克隆方法來實現。

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

圖7-2 原型模式結構圖

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

  • Prototype(抽象原型類):它是聲明克隆方法的接口,是所有具體原型類的公共父類,可以是抽象類也可以是接口,甚至還可以是具體實現類。

  • ConcretePrototype(具體原型類):它實現在抽象原型類中聲明的克隆方法,在克隆方法中返回自己的一個克隆對象。

  • Client(客戶類):讓一個原型對象克隆自身從而創建一個新的對象,在客戶類中只需要直接實例化或通過工廠方法等方式創建一個原型對象,再通過調用該對象的克隆方法即可得到多個相同的對象。由于客戶類針對抽象原型類Prototype編程,因此用戶可以根據需要選擇具體原型類,系統具有較好的可擴展性,增加或更換具體原型類都很方便。

原型模式的核心在于如何實現克隆方法,下面將介紹兩種在Java語言中常用的克隆實現方法:

  1. 通用實現方法

通用的克隆實現方法是在具體原型類的克隆方法中實例化一個與自身類型相同的對象并將其返回,并將相關的參數傳入新創建的對象中,保證它們的成員屬性相同。示意代碼如下所示:

class ConcretePrototype implements Prototype
{
    private String  attr; //成員屬性
    public void  setAttr(String attr)
    {
        this.attr = attr;
    }
    public String  getAttr()
    {
        return this.attr;
    }
    public Prototype clone() //克隆方法
    {
        Prototype  prototype = new ConcretePrototype(); //創建新對象
        prototype.setAttr(this.attr);
        return prototype;
    }
}

思考

能否將上述代碼中的clone()方法寫成:public Prototype clone() { return this; }?給出你的理由。

在客戶類中我們只需要創建一個ConcretePrototype對象作為原型對象,然后調用其clone()方法即可得到對應的克隆對象,如下代碼所示:

Prototype obj1  = new ConcretePrototype();
obj1.setAttr("Sunny");
Prototype obj2  = obj1.clone();

這種方法可作為原型模式的通用實現,它與編程語言特性無關,任何面向對象語言都可以使用這種形式來實現對原型的克隆。

  1. Java語言提供的clone()方法

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

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

class ConcretePrototype implements Cloneable
{
    //……
    @Override
    public Prototype clone()
    {
        Object object = null;
        try {
            object = super.clone();
        } catch (CloneNotSupportedException exception) {
            System.err.println("Not support cloneable");
        }
        return (Prototype )object;
    }
    //……
}

在客戶端創建原型對象和克隆對象也很簡單,如下代碼所示:

Prototype obj1  = new ConcretePrototype();
Prototype obj2  = obj1.clone();

一般而言,Java語言中的clone()方法滿足:

  1. 對任何對象x,都有x.clone() != x,即克隆對象與原型對象不是同一個對象;

  2. 對任何對象x,都有x.clone().getClass() == x.getClass(),即克隆對象與原型對象的類型一樣;

  3. 如果對象x的equals()方法定義恰當,那么x.clone().equals(x)應該成立。

為了獲取對象的一份拷貝,我們可以直接利用Object類的clone()方法,具體步驟如下:

  1. 在派生類中覆蓋基類的clone()方法,并聲明為public;

  2. 在派生類的clone()方法中,調用super.clone();

  3. 派生類需實現Cloneable接口。

此時,Object類相當于抽象原型類,所有實現了Cloneable接口的類相當于具體原型類。

對象的克隆——原型模式(二)

7.3 完整解決方案

Sunny公司開發人員決定使用原型模式來實現工作周報的快速創建,快速創建工作周報結構圖如圖7-3所示:

圖7-3 快速創建工作周報結構圖

在圖7-3中,WeeklyLog充當具體原型類,Object類充當抽象原型類,clone()方法為原型方法。WeeklyLog類的代碼如下所示:

//工作周報WeeklyLog:具體原型類,考慮到代碼的可讀性和易理解性,只列出部分與模式相關的核心代碼
class WeeklyLog implements Cloneable
{
       private  String name;
       private  String date;
       private  String content;
       public  void setName(String name) {
              this.name  = name;
       }
       public  void setDate(String date) {
              this.date  = date;
       }
       public  void setContent(String content) {
              this.content  = content;
       }
       public  String getName() {
              return  (this.name);
       }
       public  String getDate() {
              return  (this.date);
       }
       public  String getContent() {
              return  (this.content);
       }
     //克隆方法clone(),此處使用Java語言提供的克隆機制
       public WeeklyLog clone()
       {
              Object obj = null;
              try
              {
                     obj = super.clone();
                     return (WeeklyLog)obj;     
              }
              catch(CloneNotSupportedException e)
              {
                     System.out.println("不支持復制!");
                     return null;
              }
       }
}

編寫如下客戶端測試代碼:

class Client
{
   public  static void main(String args[])
   {
       WeeklyLog log_previous = new WeeklyLog();  //創建原型對象
       log_previous.setName("張無忌");
       log_previous.setDate("第12周");
       log_previous.setContent("這周工作很忙,每天加班!");

       System.out.println("****周報****");
       System.out.println("周次:" +  log_previous.getDate());
       System.out.println("姓名:" +  log_previous.getName());
       System.out.println("內容:" +  log_previous.getContent());
       System.out.println("--------------------------------");

       WeeklyLog  log_new;
       log_new  = log_previous.clone(); //調用克隆方法創建克隆對象
       log_new.setDate("第13周");
       System.out.println("****周報****");
       System.out.println("周次:" + log_new.getDate());
       System.out.println("姓名:" + log_new.getName());
       System.out.println("內容:" + log_new.getContent());
   }
}

編譯并運行程序,輸出結果如下:

****周報****
周次:第12周
姓名:張無忌
內容:這周工作很忙,每天加班!
--------------------------------
****周報****
周次:第13周
姓名:張無忌
內容:這周工作很忙,每天加班!

通過已創建的工作周報可以快速創建新的周報,然后再根據需要修改周報,無須再從頭開始創建。原型模式為工作流系統中任務單的快速生成提供了一種解決方案。

思考

如果在Client類的main()函數中增加如下幾條語句:

System.out.println(log_previous == log_new);
System.out.println(log_previous.getDate() == log_new.getDate());
System.out.println(log_previous.getName() == log_new.getName());
System.out.println(log_previous.getContent() == log_new.getContent());

預測這些語句的輸出結果。

對象的克隆——原型模式(三)

7.4 帶附件的周報

通過引入原型模式,Sunny軟件公司OA系統支持工作周報的快速克隆,極大提高了工作周報的編寫效率,受到員工的一致好評。但有員工又發現一個問題,有些工作周報帶有附件,例如經理助理“小龍女”的周報通常附有本周項目進展報告匯總表、本周客戶反饋信息匯總表等,如果使用上述原型模式來復制周報,周報雖然可以復制,但是周報的附件并不能復制,這是由于什么原因導致的呢?如何才能實現周報和附件的同時復制呢?我們在本節將討論如何解決這些問題。

在回答這些問題之前,先介紹一下兩種不同的克隆方法,淺克隆(ShallowClone)和深克隆(DeepClone)。在Java語言中,數據類型分為值類型(基本數據類型)和引用類型,值類型包括int、double、byte、boolean、char等簡單數據類型,引用類型包括類、接口、數組等復雜類型。淺克隆和深克隆的主要區別在于是否支持引用類型的成員變量的復制,下面將對兩者進行詳細介紹。

1.淺克隆

在淺克隆中,如果原型對象的成員變量是值類型,將復制一份給克隆對象;如果原型對象的成員變量是引用類型,則將引用對象的地址復制一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內存地址。簡單來說,在淺克隆中,當對象被復制時只復制它本身和其中包含的值類型的成員變量,而引用類型的成員對象并沒有復制,如圖7-4所示:

圖7-4 淺克隆示意圖

在Java語言中,通過覆蓋Object類的clone()方法可以實現淺克隆。為了讓大家更好地理解淺克隆和深克隆的區別,我們首先使用淺克隆來實現工作周報和附件類的復制,其結構如圖7-5所示:

圖7-5 帶附件的周報結構圖(淺克隆)

附件類Attachment代碼如下:

//附件類
class Attachment
{
       private  String name; //附件名
       public  void setName(String name)
       {
              this.name  = name;
       }
       public  String getName()
       {
              return  this.name;
       }
     public void download()
     {
            System.out.println("下載附件,文件名為" + name);
     }
}

修改工作周報類WeeklyLog,修改后的代碼如下:

//工作周報WeeklyLog
class WeeklyLog implements Cloneable
{
     //為了簡化設計和實現,假設一份工作周報中只有一個附件對象,實際情況中可以包含多個附件,可以通過List等集合對象來實現
     private Attachment attachment;
     private String name;
     private String date;
     private String content;
     public void setAttachment(Attachment  attachment) {
         this.attachment = attachment;
     }
     public void setName(String name) {
         this.name  = name;
     }
     public void setDate(String date) { 
         this.date  = date;
     }
     public void setContent(String content) {
         this.content  = content;
     }
     public Attachment  getAttachment(){
         return (this.attachment);
     }
     public String getName() {
         return  (this.name);
     }
     public String getDate() {
         return  (this.date);
     }
     public String getContent() {
         return  (this.content);
     }
     //使用clone()方法實現淺克隆
     public WeeklyLog clone()
     {
         Object obj = null;
         try
           {
               obj = super.clone();
               return (WeeklyLog)obj; 
           }
           catch(CloneNotSupportedException  e)
           {
               System.out.println("不支持復制!");
               return null; 
           }
     }
}

客戶端代碼如下所示:

class Client
{
    public  static void main(String args[])
    {
        WeeklyLog  log_previous, log_new;
        log_previous  = new WeeklyLog(); //創建原型對象
        Attachment  attachment = new Attachment(); //創建附件對象
        log_previous.setAttachment(attachment);  //將附件添加到周報中
        log_new  = log_previous.clone(); //調用克隆方法創建克隆對象
        //比較周報
        System.out.println("周報是否相同? " + (log_previous ==  log_new));
        //比較附件
        System.out.println("附件是否相同? " +  (log_previous.getAttachment() == log_new.getAttachment())); 
    }
}

編譯并運行程序,輸出結果如下:

周報是否相同?  false
附件是否相同? true

由于使用的是淺克隆技術,因此工作周報對象復制成功,通過“==”比較原型對象和克隆對象的內存地址時輸出false;但是比較附件對象的內存地址時輸出true,說明它們在內存中是同一個對象。

2.深克隆

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

圖7-6 深克隆示意圖

在Java語言中,如果需要實現深克隆,可以通過序列化(Serialization)等方式來實現。序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在于內存中。通過序列化實現的拷貝不僅可以復制對象本身,而且可以復制其引用的成員對象,因此通過序列化將對象寫到一個流中,再從流里將其讀出來,可以實現深克隆。需要注意的是能夠實現序列化的對象其類必須實現Serializable接口,否則無法實現序列化操作。下面我們使用深克隆技術來實現工作周報和附件對象的復制,由于要將附件對象和工作周報對象都寫入流中,因此兩個類均需要實現Serializable接口,其結構如圖7-7所示:

圖7-7 帶附件的周報結構圖(深克隆)

修改后的附件類Attachment代碼如下:

import  java.io.*;
//附件類
class  Attachment implements Serializable
{
       private  String name; //附件名
       public  void setName(String name)
       {
              this.name  = name;
       }
       public  String getName()
       {
              return  this.name;
       }
     public void download()
     {
            System.out.println("下載附件,文件名為" + name);
     }
}

工作周報類WeeklyLog不再使用Java自帶的克隆機制,而是通過序列化來從頭實現對象的深克隆,我們需要重新編寫clone()方法,修改后的代碼如下:

import  java.io.*;
//工作周報類
class  WeeklyLog implements Serializable
{
       private  Attachment attachment;
       private  String name;
       private  String date;
       private  String content;
       public  void setAttachment(Attachment attachment) {
              this.attachment  = attachment;
       }
       public  void setName(String name) {
              this.name  = name;
       }
       public  void setDate(String date) {
              this.date  = date;
       }
       public  void setContent(String content) {
              this.content  = content;
       }
       public  Attachment getAttachment(){
              return  (this.attachment);
       }
       public  String getName() {
              return  (this.name);
       }
       public  String getDate() {
              return  (this.date);
       }
       public  String getContent() {
              return  (this.content);
       }
   //使用序列化技術實現深克隆
       public WeeklyLog deepClone() throws  IOException, ClassNotFoundException, OptionalDataException
       {
              //將對象寫入流中
              ByteArrayOutputStream bao=new  ByteArrayOutputStream();
              ObjectOutputStream oos=new  ObjectOutputStream(bao);
              oos.writeObject(this);

              //將對象從流中取出
              ByteArrayInputStream bis=new  ByteArrayInputStream(bao.toByteArray());
              ObjectInputStream ois=new  ObjectInputStream(bis);
              return  (WeeklyLog)ois.readObject();
       }
}

客戶端代碼如下所示:

class Client
{
       public  static void main(String args[])
       {
              WeeklyLog  log_previous, log_new = null;
              log_previous  = new WeeklyLog(); //創建原型對象
              Attachment  attachment = new Attachment(); //創建附件對象
              log_previous.setAttachment(attachment);  //將附件添加到周報中
              try
              {
                     log_new =  log_previous.deepClone(); //調用深克隆方法創建克隆對象                  
              }
              catch(Exception e)
              {
                     System.err.println("克隆失敗!");
              }
              //比較周報
              System.out.println("周報是否相同? " + (log_previous ==  log_new));
              //比較附件
              System.out.println("附件是否相同? " +  (log_previous.getAttachment() == log_new.getAttachment()));
       }
}

編譯并運行程序,輸出結果如下:

周報是否相同?  false
附件是否相同?  false

從輸出結果可以看出,由于使用了深克隆技術,附件對象也得以復制,因此用“==”比較原型對象的附件和克隆對象的附件時輸出結果均為false。深克隆技術實現了原型對象和克隆對象的完全獨立,對任意克隆對象的修改都不會給其他對象產生影響,是一種更為理想的克隆實現方式。

擴展

Java語言提供的Cloneable接口和Serializable接口的代碼非常簡單,它們都是空接口,這種空接口也稱為標識接口,標識接口中沒有任何方法的定義,其作用是告訴JRE這些接口的實現類是否具有某個功能,如是否支持克隆、是否支持序列化等。

對象的克隆——原型模式(四)

7.5 原型管理器的引入和實現

原型管理器(Prototype Manager)是將多個原型對象存儲在一個集合中供客戶端使用,它是一個專門負責克隆對象的工廠,其中定義了一個集合用于存儲原型對象,如果需要某個原型對象的一個克隆,可以通過復制集合中對應的原型對象來獲得。在原型管理器中針對抽象原型類進行編程,以便擴展。其結構如圖7-8所示:

圖7-8 帶原型管理器的原型模式

下面通過模擬一個簡單的公文管理器來介紹原型管理器的設計與實現: Sunny軟件公司在日常辦公中有許多公文需要創建、遞交和審批,例如《可行性分析報告》、《立項建議書》、《軟件需求規格說明書》、《項目進展報告》等,為了提高工作效率,在OA系統中為各類公文均創建了模板,用戶可以通過這些模板快速創建新的公文,這些公文模板需要統一進行管理,系統根據用戶請求的不同生成不同的新公文。

我們使用帶原型管理器的原型模式實現公文管理器的設計,其結構如圖7-9所示:

圖7-9 公文管理器結構圖

以下是實現該功能的一些核心代碼,考慮到代碼的可讀性,我們對所有的類都進行了簡化:

import java.util.*;

//抽象公文接口,也可定義為抽象類,提供clone()方法的實現,將業務方法聲明為抽象方法
interface OfficialDocument extends  Cloneable
{
       public  OfficialDocument clone();
       public  void display();
}

//可行性分析報告(Feasibility Analysis Report)類
class FAR implements OfficialDocument
{
       public  OfficialDocument clone()
      {
              OfficialDocument  far = null;
              try
              {
                     far  = (OfficialDocument)super.clone();
              }
              catch(CloneNotSupportedException  e)
              {
                      System.out.println("不支持復制!");
              }
              return  far;
       }

       public  void display()
       {
              System.out.println("《可行性分析報告》");
       }
}

//軟件需求規格說明書(Software Requirements Specification)類
class SRS implements OfficialDocument
{
       public  OfficialDocument clone()
       {
              OfficialDocument  srs = null;
              try
              {
                     srs  = (OfficialDocument)super.clone();
              }
              catch(CloneNotSupportedException  e)
              { 
                     System.out.println("不支持復制!");
              }
              return  srs;
       }

       public  void display()
       {
              System.out.println("《軟件需求規格說明書》");
       }
}

//原型管理器(使用餓漢式單例實現)
class  PrototypeManager
{
       //定義一個Hashtable,用于存儲原型對象
       private Hashtable ht=new Hashtable();
       private static PrototypeManager pm =  new PrototypeManager();

       //為Hashtable增加公文對象   
     private  PrototypeManager()
     {
              ht.put("far",new  FAR());
              ht.put("srs",new  SRS());               
     }

     //增加新的公文對象
       public void addOfficialDocument(String  key,OfficialDocument doc)
       {
              ht.put(key,doc);
       }

       //通過淺克隆獲取新的公文對象
       public OfficialDocument  getOfficialDocument(String key)
       {
              return  ((OfficialDocument)ht.get(key)).clone();
       }

       public static PrototypeManager  getPrototypeManager()
       {
              return pm;
       }
}

客戶端代碼如下所示:

class Client
{
       public  static void main(String args[])
       {
              //獲取原型管理器對象
              PrototypeManager pm =  PrototypeManager.getPrototypeManager();  

              OfficialDocument  doc1,doc2,doc3,doc4;

              doc1  = pm.getOfficialDocument("far");
              doc1.display();
              doc2  = pm.getOfficialDocument("far");
              doc2.display();
              System.out.println(doc1  == doc2);

              doc3  = pm.getOfficialDocument("srs");
              doc3.display();
              doc4  = pm.getOfficialDocument("srs");
              doc4.display();
              System.out.println(doc3  == doc4);
       }
}

編譯并運行程序,輸出結果如下:

《可行性分析報告》
《可行性分析報告》
false
《軟件需求規格說明書》
《軟件需求規格說明書》
false

在PrototypeManager中定義了一個Hashtable類型的集合對象,使用“鍵值對”來存儲原型對象,客戶端可以通過Key(如“far”或“srs”)來獲取對應原型對象的克隆對象。PrototypeManager類提供了類似工廠方法的getOfficialDocument()方法用于返回一個克隆對象。在本實例代碼中,我們將PrototypeManager設計為單例類,使用餓漢式單例實現,確保系統中有且僅有一個PrototypeManager對象,有利于節省系統資源,并可以更好地對原型管理器對象進行控制。

思考

如果需要增加一種新類型的公文,如《項目進展報告》(Project Progress Report, PPR),公文管理器系統源代碼如何修改,動手實踐你的修改方案。

7.6 原型模式總結

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

  1. 主要優點

原型模式的主要優點如下:

(1) 當創建新的對象實例較為復雜時,使用原型模式可以簡化對象的創建過程,通過復制一個已有實例可以提高新實例的創建效率。

(2) 擴展性較好,由于在原型模式中提供了抽象原型類,在客戶端可以針對抽象原型類進行編程,而將具體原型類寫在配置文件中,增加或減少產品類對原有系統都沒有任何影響。

(3) 原型模式提供了簡化的創建結構,工廠方法模式常常需要有一個與產品類等級結構相同的工廠等級結構,而原型模式就不需要這樣,原型模式中產品的復制是通過封裝在原型類中的克隆方法實現的,無須專門的工廠類來創建產品。

(4) 可以使用深克隆的方式保存對象的狀態,使用原型模式將對象復制一份并將其狀態保存起來,以便在需要的時候使用(如恢復到某一歷史狀態),可輔助實現撤銷操作。

  1. 主要缺點

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

(1) 需要為每一個類配備一個克隆方法,而且該克隆方法位于一個類的內部,當對已有的類進行改造時,需要修改源代碼,違背了“開閉原則”。

(2) 在實現深克隆時需要編寫較為復雜的代碼,而且當對象之間存在多重的嵌套引用時,為了實現深克隆,每一層對象對應的類都必須支持深克隆,實現起來可能會比較麻煩。

  1. 適用場景

在以下情況下可以考慮使用原型模式:

(1) 創建新對象成本較大(如初始化需要占用較長的時間,占用太多的CPU資源或網絡資源),新的對象可以通過原型模式對已有對象進行復制來獲得,如果是相似對象,則可以對其成員變量稍作修改。

(2) 如果系統要保存對象的狀態,而對象的狀態變化很小,或者對象本身占用內存較少時,可以使用原型模式配合備忘錄模式來實現。

(3) 需要避免使用分層次的工廠類來創建分層次的對象,并且類的實例對象只有一個或很少的幾個組合狀態,通過復制原型對象得到新實例可能比使用構造函數創建一個新實例更加方便。

練習

設計并實現一個客戶類Customer,其中包含一個名為客戶地址的成員變量,客戶地址的類型為Address,用淺克隆和深克隆分別實現Customer對象的復制并比較這兩種克隆方式的異同。

練習會在我的github上做掉

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

推薦閱讀更多精彩內容