【創(chuàng)建型模式六】原型模式(Prototype)

1 場(chǎng)景問(wèn)題#

1.1 訂單處理系統(tǒng)##

考慮這樣一個(gè)實(shí)際應(yīng)用:訂單處理系統(tǒng)。

現(xiàn)在有一個(gè)訂單處理的系統(tǒng),里面有個(gè)保存訂單的業(yè)務(wù)功能,在這個(gè)業(yè)務(wù)功能里面,客戶有這么一個(gè)需求:每當(dāng)訂單的預(yù)定產(chǎn)品數(shù)量超過(guò)1000的時(shí)候,就需要把訂單拆成兩份訂單來(lái)保存,如果拆成兩份訂單后,還是超過(guò)1000,那就繼續(xù)拆分,直到每份訂單的預(yù)定產(chǎn)品數(shù)量不超過(guò)1000。至于為什么要拆分,原因是好進(jìn)行訂單的后續(xù)處理,后續(xù)是由人工來(lái)處理,每個(gè)人工工作小組的處理能力上限是1000。

根據(jù)業(yè)務(wù),目前的訂單類型被分成兩種:一種是個(gè)人訂單,一種是公司訂單。現(xiàn)在想要實(shí)現(xiàn)一個(gè)通用的訂單處理系統(tǒng),也就是說(shuō),不管具體是什么類型的訂單,都要能夠正常的處理。

該怎么實(shí)現(xiàn)呢?

1.2 不用模式的解決方案##

來(lái)分析上面要求實(shí)現(xiàn)的功能,有朋友會(huì)想,這很簡(jiǎn)單嘛,一共就一個(gè)功能,沒(méi)什么困難的,真的是這樣嗎?先來(lái)嘗試著實(shí)現(xiàn)看看。

  1. 定義訂單接口

首先,要想實(shí)現(xiàn)通用的訂單處理,而不關(guān)心具體的訂單類型,那么很明顯,訂單處理的對(duì)象應(yīng)該面向一個(gè)訂單的接口或是一個(gè)通用的訂單對(duì)象來(lái)編程,這里就選用面向訂單接口來(lái)處理吧,先把這個(gè)訂單接口定義出來(lái),示例代碼如下:

/**
   * 訂單的接口
   */
public interface OrderApi {
    /**
     * 獲取訂單產(chǎn)品數(shù)量
     * @return 訂單中產(chǎn)品數(shù)量
     */
    public int getOrderProductNum();
    /**
     * 設(shè)置訂單產(chǎn)品數(shù)量
     * @param num 訂單產(chǎn)品數(shù)量
     */
    public void setOrderProductNum(int num);
}
  1. 既然定義好了訂單的接口,那么接下來(lái)把各種類型的訂單實(shí)現(xiàn)出來(lái),先看看個(gè)人的訂單實(shí)現(xiàn),示例代碼如下:
/**
   * 個(gè)人訂單對(duì)象
   */
public class PersonalOrder implements OrderApi{
    /**
     * 訂購(gòu)人員姓名
     */
    private String customerName;
    /**
     * 產(chǎn)品編號(hào)
     */
    private String productId;
    /**
     * 訂單產(chǎn)品數(shù)量
     */
    private int orderProductNum = 0;

    public int getOrderProductNum() {
       return this.orderProductNum;
    }  
    public void setOrderProductNum(int num) {
       this.orderProductNum = num;
    }  
    public String getCustomerName() {
       return customerName;
    }
    public void setCustomerName(String customerName) {
       this.customerName = customerName;
    }
    public String getProductId() {
       return productId;
    }
    public void setProductId(String productId) {
       this.productId = productId;
    }
    public String toString(){
       return "本個(gè)人訂單的訂購(gòu)人是="+this.customerName+",訂購(gòu)產(chǎn)品是="+this.productId+",訂購(gòu)數(shù)量為="+this.orderProductNum;
    }
}

再看看企業(yè)訂單的實(shí)現(xiàn),示例代碼如下:

/**
   * 企業(yè)訂單對(duì)象
   */
public class EnterpriseOrder implements OrderApi {
    /**
     * 企業(yè)名稱
     */
    private String enterpriseName;
    /**
     * 產(chǎn)品編號(hào)
     */
    private String productId;  
    /**
     * 訂單產(chǎn)品數(shù)量
     */
    private int orderProductNum = 0;

    public int getOrderProductNum() {
       return this.orderProductNum;
    }  
    public void setOrderProductNum(int num) {
       this.orderProductNum = num;
    }  
    public String getEnterpriseName() {
       return enterpriseName;
    }
    public void setEnterpriseName(String enterpriseName) {
       this.enterpriseName = enterpriseName;
    }
    public String getProductId() {
       return productId;
    }
    public void setProductId(String productId) {
       this.productId = productId;
    }
    public String toString(){
       return "本企業(yè)訂單的訂購(gòu)企業(yè)是="+this.enterpriseName+",訂購(gòu)產(chǎn)品是="+this.productId+",訂購(gòu)數(shù)量為="+this.orderProductNum;
    }
}

有些朋友看到這里,可能會(huì)有這樣的疑問(wèn):看上去上面兩種類型的訂單對(duì)象,僅僅是一個(gè)數(shù)據(jù)封裝的對(duì)象,而且還有一些數(shù)據(jù)是相同的,為何不抽出一個(gè)父類來(lái),把共同的數(shù)據(jù)定義在父類里面呢?

這里有兩個(gè)考慮,一個(gè)是:這里僅僅是一個(gè)示意,實(shí)際情況遠(yuǎn)比這復(fù)雜,實(shí)際開(kāi)發(fā)中不會(huì)僅僅是數(shù)據(jù)封裝對(duì)象這么簡(jiǎn)單。另外一個(gè)是:為了后續(xù)示例的重點(diǎn)突出,這里要學(xué)習(xí)的是原型模式,因此就沒(méi)有去抽取父類,以免對(duì)象層級(jí)過(guò)多,影響主題的展示。

  1. 實(shí)現(xiàn)好了訂單對(duì)象,接下來(lái)看看如何實(shí)現(xiàn)通用的訂單處理,先把訂單處理的對(duì)象大概定義出來(lái),示例代碼如下:
/**
   * 處理訂單的業(yè)務(wù)對(duì)象
   */
public class OrderBusiness {
    /**
     * 創(chuàng)建訂單的方法
     * @param order 訂單的接口對(duì)象
     */
    public void saveOrder(OrderApi order){
        //等待具體實(shí)現(xiàn)
    }
}

現(xiàn)在的中心任務(wù)就是要來(lái)實(shí)現(xiàn)這個(gè)saveOrder的方法,傳入的參數(shù)是一個(gè)訂單的接口對(duì)象,這個(gè)方法要實(shí)現(xiàn)的功能:根據(jù)業(yè)務(wù)要求,當(dāng)訂單的預(yù)定產(chǎn)品數(shù)量超過(guò)1000的時(shí)候,就需要把訂單拆成兩份訂單。

那好,來(lái)嘗試著實(shí)現(xiàn)一下,因?yàn)轭A(yù)定的數(shù)量可能會(huì)很大,因此采用一個(gè)while循環(huán)來(lái)處理,直到拆分后訂單的數(shù)量不超過(guò)1000,先把實(shí)現(xiàn)的思路寫出來(lái),示例代碼如下:

public class OrderBusiness {
    public void saveOrder(OrderApi order){
        //1:判斷當(dāng)前的預(yù)定產(chǎn)品數(shù)量是否大于1000
        while(order.getOrderProductNum() > 1000){
            //2:如果大于,還需要繼續(xù)拆分
            //2.1再新建一份訂單,跟傳入的訂單除了數(shù)量不一樣外,其它都相同
            OrderApi newOrder = null;

            ...
        }
    }
}

大家會(huì)發(fā)現(xiàn),才剛寫到第二步就寫不下去了,為什么呢?

因?yàn)楝F(xiàn)在判斷需要拆分訂單,也就是需要新建一個(gè)訂單對(duì)象,可是訂單處理對(duì)象面對(duì)的是訂單的接口,它根本就不知道現(xiàn)在訂單具體的類型,也不知道具體的訂單實(shí)現(xiàn),它無(wú)法創(chuàng)建出新的訂單對(duì)象來(lái),也就無(wú)法實(shí)現(xiàn)訂單拆分的功能了。

  1. 一個(gè)簡(jiǎn)單的解決辦法

有朋友提供了這么一個(gè)解決的思路,他說(shuō):不就是在saveOrder方法里面不知道具體的類型,從而導(dǎo)致無(wú)法創(chuàng)建對(duì)象嗎?很簡(jiǎn)單,使用instanceof來(lái)判斷不就可以了,他還給出了他的實(shí)現(xiàn)示意,示意代碼如下:

public class OrderBusiness {
    public void saveOrder(OrderApi order){
       while(order.getOrderProductNum() > 1000)
           //定義一個(gè)表示被拆分出來(lái)的新訂單對(duì)象
           OrderApi newOrder = null;         
           if(order instanceof PersonalOrder){
              //創(chuàng)建相應(yīng)的訂單對(duì)象
              PersonalOrder p2 = new PersonalOrder();
              //然后進(jìn)行賦值等,省略了
              //然后再設(shè)置給newOrder
              newOrder = p2;
           }else if(order instanceof EnterpriseOrder){
               //創(chuàng)建相應(yīng)的訂單對(duì)象
              EnterpriseOrder e2 = new EnterpriseOrder();
              //然后進(jìn)行賦值等,省略了
              //然后再設(shè)置給newOrder
              newOrder = e2;
           }         
           //然后進(jìn)行拆分和其它業(yè)務(wù)功能處理,省略了
       }     
    }
}

好像能解決問(wèn)題,對(duì)吧。那我們就來(lái)按照他提供的思路,把這個(gè)通用的訂單處理對(duì)象實(shí)現(xiàn)出來(lái),示例代碼如下:

/**
   * 處理訂單的業(yè)務(wù)對(duì)象
   */
public class OrderBusiness {
    /**
     * 創(chuàng)建訂單的方法
     * @param order 訂單的接口對(duì)象
     */
    public void saveOrder(OrderApi order){
       //根據(jù)業(yè)務(wù)要求,當(dāng)訂單預(yù)定產(chǎn)品數(shù)量超過(guò)1000時(shí),就要把訂單拆成兩份訂單
       //當(dāng)然如果要做好,這里的1000應(yīng)該做成常量,這么做是為了演示簡(jiǎn)單

       //1:判斷當(dāng)前的預(yù)定產(chǎn)品數(shù)量是否大于1000
       while(order.getOrderProductNum() > 1000){
           //2:如果大于,還需要繼續(xù)拆分
           //2.1再新建一份訂單,跟傳入的訂單除了數(shù)量不一樣外,其它都相同
           OrderApi newOrder = null;
           if(order instanceof PersonalOrder){
              //創(chuàng)建相應(yīng)的新的訂單對(duì)象
              PersonalOrder p2 = new PersonalOrder();
              //然后進(jìn)行賦值,但是產(chǎn)品數(shù)量為1000
              PersonalOrder p1 = (PersonalOrder)order;
              p2.setCustomerName(p1.getCustomerName());
              p2.setProductId(p1.getProductId());          
              p2.setOrderProductNum(1000);
              //然后再設(shè)置給newOrder
              newOrder = p2;
           }else if(order instanceof EnterpriseOrder){
              //創(chuàng)建相應(yīng)的訂單對(duì)象
              EnterpriseOrder e2 = new EnterpriseOrder();
              //然后進(jìn)行賦值,但是產(chǎn)品數(shù)量為1000
              EnterpriseOrder e1 = (EnterpriseOrder)order;
              e2.setEnterpriseName(e1.getEnterpriseName());
              e2.setProductId(e1.getProductId());
              e2.setOrderProductNum(1000);
              //然后再設(shè)置給newOrder
              newOrder = e2;
           }         

           //2.2原來(lái)的訂單保留,把數(shù)量設(shè)置成減少1000
           order.setOrderProductNum(order.getOrderProductNum()-1000);

           //然后是業(yè)務(wù)功能處理,省略了,打印輸出,看一下
           System.out.println("拆分生成訂單=="+newOrder);
       }     
       //3:不超過(guò)1000,那就直接業(yè)務(wù)功能處理,省略了,打印輸出,看一下
       System.out.println("訂單=="+order);   
    }
}
  1. 寫個(gè)客戶端來(lái)測(cè)試一下,示例代碼如下:
public class OrderClient {
    public static void main(String[] args) {
       //創(chuàng)建訂單對(duì)象,這里為了演示簡(jiǎn)單,直接new了
       PersonalOrder op = new PersonalOrder();
       //設(shè)置訂單數(shù)據(jù)
       op.setOrderProductNum(2925);
       op.setCustomerName("張三");
       op.setProductId("P0001");

       //這里獲取業(yè)務(wù)處理的類,也直接new了,為了簡(jiǎn)單,連業(yè)務(wù)接口都沒(méi)有做
       OrderBusiness ob = new OrderBusiness();
       //調(diào)用業(yè)務(wù)來(lái)保存訂單對(duì)象
       ob.saveOrder(op);
    }
}

運(yùn)行結(jié)果如下:

拆分生成訂單==本個(gè)人訂單的訂購(gòu)人是=張三,訂購(gòu)產(chǎn)品是=P0001,訂購(gòu)數(shù)量為=1000
拆分生成訂單==本個(gè)人訂單的訂購(gòu)人是=張三,訂購(gòu)產(chǎn)品是=P0001,訂購(gòu)數(shù)量為=1000
訂單==本個(gè)人訂單的訂購(gòu)人是=張三,訂購(gòu)產(chǎn)品是=P0001,訂購(gòu)數(shù)量為=925

根據(jù)訂單中訂購(gòu)產(chǎn)品的數(shù)量,一份訂單被拆分成了三份。同樣的,你還可以傳入企業(yè)訂單,看看是否能正常滿足功能要求。

1.3 有何問(wèn)題##

看起來(lái),上面的實(shí)現(xiàn)確實(shí)不難,好像也能夠通用的進(jìn)行訂單處理,而不需要關(guān)心訂單的類型和具體實(shí)現(xiàn)這樣的功能。

仔細(xì)想想,真的沒(méi)有關(guān)心訂單的類型和具體實(shí)現(xiàn)嗎?答案是“否定的”。

事實(shí)上,在實(shí)現(xiàn)訂單處理的時(shí)候,上面的實(shí)現(xiàn)是按照訂單的類型和具體實(shí)現(xiàn)來(lái)處理的,就是instanceof的那一段。有朋友可能會(huì)問(wèn),這樣實(shí)現(xiàn)有何不可嗎?這樣的實(shí)現(xiàn)有如下幾個(gè)問(wèn)題:

既然想要實(shí)現(xiàn)通用的訂單處理,那么對(duì)于訂單處理的實(shí)現(xiàn)對(duì)象,是不應(yīng)該知道訂單的具體實(shí)現(xiàn)的,更不應(yīng)該依賴訂單的具體實(shí)現(xiàn)。但是上面的實(shí)現(xiàn)中,很明顯訂單處理的對(duì)象依賴了訂單的具體實(shí)現(xiàn)對(duì)象。

這種實(shí)現(xiàn)方式另外一個(gè)問(wèn)題就是:難以擴(kuò)展新的訂單類型。假如現(xiàn)在要加入一個(gè)大客戶專用訂單的類型,那么就需要修改訂單處理的對(duì)象,要在里面添加對(duì)新的訂單類型的支持,這算哪門子的通用處理。

因此,上面的實(shí)現(xiàn)是不太好的,把上面的問(wèn)題再抽象描述一下:已經(jīng)有了某個(gè)對(duì)象實(shí)例后,如何能夠快速簡(jiǎn)單地創(chuàng)建出更多的這種對(duì)象?比如上面的問(wèn)題,就是已經(jīng)有了訂單接口類型的對(duì)象實(shí)例,然后在方法中需要?jiǎng)?chuàng)建出更多的這種對(duì)象。怎么解決呢?

2 解決方案#

2.1 原型模式來(lái)解決##

用來(lái)解決上述問(wèn)題的一個(gè)合理的解決方案就是原型模式。那么什么是原型模式呢?

  1. 原型模式定義
原型模式定義
  1. 應(yīng)用原型模式來(lái)解決的思路

仔細(xì)分析上面的問(wèn)題,在saveOrder方法里面,已經(jīng)有了訂單接口類型的對(duì)象實(shí)例,是從外部傳入的,但是這里只是知道這個(gè)實(shí)例對(duì)象的種類是訂單的接口類型,并不知道其具體的實(shí)現(xiàn)類型,也就是不知道它到底是個(gè)人訂單還是企業(yè)訂單,但是現(xiàn)在需要在這個(gè)方法里面創(chuàng)建一個(gè)這樣的訂單對(duì)象,看起來(lái)就像是要通過(guò)接口來(lái)創(chuàng)建對(duì)象一樣。

原型模式就可以解決這樣的問(wèn)題,原型模式會(huì)要求對(duì)象實(shí)現(xiàn)一個(gè)可以“克隆”自身的接口,這樣就可以通過(guò)拷貝或者是克隆一個(gè)實(shí)例對(duì)象本身,來(lái)創(chuàng)建一個(gè)新的實(shí)例。如果把這個(gè)方法定義在接口上,看起來(lái)就像是通過(guò)接口來(lái)創(chuàng)建了新的接口對(duì)象。

這樣一來(lái),通過(guò)原型實(shí)例創(chuàng)建新的對(duì)象,就不再需要關(guān)心這個(gè)實(shí)例本身的類型,也不關(guān)心它的具體實(shí)現(xiàn),只要它實(shí)現(xiàn)了克隆自身的方法,就可以通過(guò)這個(gè)方法來(lái)獲取新的對(duì)象,而無(wú)須再去通過(guò)new來(lái)創(chuàng)建。

2.2 模式結(jié)構(gòu)和說(shuō)明##

原型模式結(jié)構(gòu)

Prototype:聲明一個(gè)克隆自身的接口,用來(lái)約束想要克隆自己的類,要求它們都要實(shí)現(xiàn)這里定義的克隆方法。

ConcretePrototype:實(shí)現(xiàn)Prototype接口的類,這些類真正實(shí)現(xiàn)了克隆自身的功能。

Client:使用原型的客戶端,首先要獲取到原型實(shí)例對(duì)象,然后通過(guò)原型實(shí)例克隆自身來(lái)創(chuàng)建新的對(duì)象實(shí)例。

2.3 原型模式示例代碼##

  1. 先來(lái)看看原型接口的定義,示例代碼如下:
/**
   * 聲明一個(gè)克隆自身的接口
   */
public interface Prototype {
    /**
     * 克隆自身的方法
     * @return 一個(gè)從自身克隆出來(lái)的對(duì)象
     */
    public Prototype clone();
}
  1. 接下來(lái)看看具體的原型實(shí)現(xiàn)對(duì)象,示例代碼如下:
/**
   * 克隆的具體實(shí)現(xiàn)對(duì)象
   */
public class ConcretePrototype1 implements Prototype {
    public Prototype clone() {
       //最簡(jiǎn)單的克隆,新建一個(gè)自身對(duì)象,由于沒(méi)有屬性,就不去復(fù)制值了
       Prototype prototype = new ConcretePrototype1();
       return prototype;
    }
}

/**
 * 克隆的具體實(shí)現(xiàn)對(duì)象
 */
public class ConcretePrototype2 implements Prototype {
    public Prototype clone() {
       //最簡(jiǎn)單的克隆,新建一個(gè)自身對(duì)象,由于沒(méi)有屬性,就不去復(fù)制值了
       Prototype prototype = new ConcretePrototype2();
       return prototype;
    }
}

為了跟上面原型模式的結(jié)構(gòu)示意圖保持一致,因此這兩個(gè)具體的原型實(shí)現(xiàn)對(duì)象,都沒(méi)有定義屬性。事實(shí)上,在實(shí)際使用原型模式的應(yīng)用中,原型對(duì)象多是有屬性的,克隆原型的時(shí)候也是需要克隆原型對(duì)象的屬性的,特此說(shuō)明一下

  1. 再看看使用原型的客戶端,示例代碼如下:
/**
   * 使用原型的客戶端
   */
public class Client {
    /**
     * 持有需要使用的原型接口對(duì)象
     */
    private Prototype prototype;
    /**
     * 構(gòu)造方法,傳入需要使用的原型接口對(duì)象
     * @param prototype 需要使用的原型接口對(duì)象
     */
    public Client(Prototype prototype){
       this.prototype = prototype;
    }
    /**
     * 示意方法,執(zhí)行某個(gè)功能操作
     */
    public void operation(){
       //會(huì)需要?jiǎng)?chuàng)建原型接口的對(duì)象
       Prototype newPrototype = prototype.clone();
    }
}

2.4 使用原型模式重寫示例##

要使用原型模式來(lái)重寫示例,先要在訂單的接口上定義出克隆的接口,然后要求各個(gè)具體的訂單對(duì)象克隆自身,這樣就可以解決:在訂單處理對(duì)象里面通過(guò)訂單接口來(lái)創(chuàng)建新的訂單對(duì)象的問(wèn)題。

使用原型模式來(lái)重寫示例的結(jié)構(gòu)如圖所示:

原型模式來(lái)重寫示例的結(jié)構(gòu)
  1. 復(fù)制誰(shuí)和誰(shuí)來(lái)復(fù)制的問(wèn)題

有了一個(gè)對(duì)象實(shí)例,要快速的創(chuàng)建跟它一樣的實(shí)例,最簡(jiǎn)單的辦法就是復(fù)制?這里又有兩個(gè)小的問(wèn)題:

復(fù)制誰(shuí)呢?當(dāng)然是復(fù)制這個(gè)對(duì)象實(shí)例,復(fù)制實(shí)例的意思是連帶著數(shù)據(jù)一起復(fù)制。

誰(shuí)來(lái)復(fù)制呢?應(yīng)該讓這個(gè)類的實(shí)例自己來(lái)復(fù)制,自己復(fù)制自己。

可是每個(gè)對(duì)象不會(huì)那么聽(tīng)話,自己去實(shí)現(xiàn)復(fù)制自己的。于是原型模式?jīng)Q定對(duì)這些對(duì)象實(shí)行強(qiáng)制要求,給這些對(duì)象定義一個(gè)接口,在接口里面定義一個(gè)方法,這個(gè)方法用來(lái)要求每個(gè)對(duì)象實(shí)現(xiàn)自己復(fù)制自己

由于現(xiàn)在存在訂單的接口,因此就把這個(gè)要求克隆自身的方法定義在訂單的接口里面,示例代碼如下:

/**
   * 訂單的接口,聲明了可以克隆自身的方法
   */
public interface OrderApi {
    public int getOrderProductNum();
    public void setOrderProductNum(int num);
    /**
     * 克隆方法
     * @return 訂單原型的實(shí)例
     */
    public OrderApi cloneOrder();
}
  1. 如何克隆

定義好了克隆的接口,那么在訂單的實(shí)現(xiàn)類里面,就得讓它實(shí)現(xiàn)這個(gè)接口,并具體的實(shí)現(xiàn)這個(gè)克隆方法,新的問(wèn)題出來(lái)了,如何實(shí)現(xiàn)克隆呢?

很簡(jiǎn)單,只要先new一個(gè)自己對(duì)象的實(shí)例,然后把自己實(shí)例中的數(shù)據(jù)取出來(lái),設(shè)置到新的對(duì)象實(shí)例中去,不就可以完成實(shí)例的復(fù)制了嘛,復(fù)制的結(jié)果就是有了一個(gè)跟自身一模一樣的實(shí)例。

/**
   * 個(gè)人訂單對(duì)象
   */
public class PersonalOrder implements OrderApi{
    private String customerName;
    private String productId;
    private int orderProductNum = 0;

    public int getOrderProductNum() {
       return this.orderProductNum;
    }  
    public void setOrderProductNum(int num) {
       this.orderProductNum = num;
    }  
    public String getCustomerName() {
       return customerName;
    }
    public void setCustomerName(String customerName) {
       this.customerName = customerName;
    }
    public String getProductId() {
       return productId;
    }
    public void setProductId(String productId) {
       this.productId = productId;
    }
    public String toString(){
       return "本個(gè)人訂單的訂購(gòu)人是="+this.customerName+",訂購(gòu)產(chǎn)品是="+this.productId+",訂購(gòu)數(shù)量為="+this.orderProductNum;
    }  
    public OrderApi cloneOrder() {
       //創(chuàng)建一個(gè)新的訂單,然后把本實(shí)例的數(shù)據(jù)復(fù)制過(guò)去
       PersonalOrder order = new PersonalOrder();
       order.setCustomerName(this.customerName);
       order.setProductId(this.productId);
       order.setOrderProductNum(this.orderProductNum);

       return order;
    }
}

接下來(lái)看看企業(yè)訂單的具體實(shí)現(xiàn),示例代碼如下:

/**
   * 企業(yè)訂單對(duì)象
   */
public class EnterpriseOrder implements OrderApi{
    private String enterpriseName;
    private String productId;  
    private int orderProductNum = 0;
    public int getOrderProductNum() {
       return this.orderProductNum;
    }  
    public void setOrderProductNum(int num) {
       this.orderProductNum = num;
    }  
    public String getEnterpriseName() {
       return enterpriseName;
    }
    public void setEnterpriseName(String enterpriseName) {
       this.enterpriseName = enterpriseName;
    }
    public String getProductId() {
       return productId;
    }
    public void setProductId(String productId) {
       this.productId = productId;
    }
    public String toString(){
       return "本企業(yè)訂單的訂購(gòu)企業(yè)是="+this.enterpriseName+",訂購(gòu)產(chǎn)品是="+this.productId+",訂購(gòu)數(shù)量為="+this.orderProductNum;
    }
    public OrderApi cloneOrder() {
       //創(chuàng)建一個(gè)新的訂單,然后把本實(shí)例的數(shù)據(jù)復(fù)制過(guò)去
       EnterpriseOrder order = new EnterpriseOrder();
       order.setEnterpriseName(this.enterpriseName);
       order.setProductId(this.productId);
       order.setOrderProductNum(this.orderProductNum);
       return order;
    }  
}
  1. 使用克隆方法

這里使用訂單接口的克隆方法的,是訂單的處理對(duì)象,也就是說(shuō),訂單的處理對(duì)象就相當(dāng)于原型模式結(jié)構(gòu)中的Client。

當(dāng)然,客戶端在調(diào)用clone方法之前,還需要先獲得相應(yīng)的實(shí)例對(duì)象,有了實(shí)例對(duì)象,才能調(diào)用該實(shí)例對(duì)象的clone方法。

這里使用克隆方法的時(shí)候,跟標(biāo)準(zhǔn)的原型實(shí)現(xiàn)有一些不同,在標(biāo)準(zhǔn)的原型實(shí)現(xiàn)的示例代碼里面,客戶端是持有需要克隆的對(duì)象,而這里變化成了通過(guò)方法傳入需要使用克隆的對(duì)象,這點(diǎn)大家注意一下。示例代碼如下:

public class OrderBusiness {
    /**
     * 創(chuàng)建訂單的方法
     * @param order 訂單的接口對(duì)象
     */
    public void saveOrder(OrderApi order){
       //1:判斷當(dāng)前的預(yù)定產(chǎn)品數(shù)量是否大于1000
       while(order.getOrderProductNum() > 1000){
           //2:如果大于,還需要繼續(xù)拆分
           //2.1再新建一份訂單,跟傳入的訂單除了數(shù)量不一樣外,其它都相同
           OrderApi newOrder = order.cloneOrder();
           //然后進(jìn)行賦值,產(chǎn)品數(shù)量為1000
           newOrder.setOrderProductNum(1000);

           //2.2原來(lái)的訂單保留,把數(shù)量設(shè)置成減少1000
           order.setOrderProductNum(order.getOrderProductNum()-1000);

           //然后是業(yè)務(wù)功能處理,省略了,打印輸出,看一下
           System.out.println("拆分生成訂單=="+newOrder);
       }     
       //3:不超過(guò),那就直接業(yè)務(wù)功能處理,省略了,打印輸出,看一下
       System.out.println("訂單=="+order);
    }
}

2.5 小提示##

看到這里,可能有些朋友會(huì)認(rèn)為:Java的Object里面本身就有clone方法,還用搞得這么麻煩嗎?

雖然Java里面有clone方法,上面這么做還是很有意義的,可以讓我們更好的、更完整的體會(huì)原型這個(gè)設(shè)計(jì)模式。當(dāng)然,后面會(huì)講述如何使用Java里面的clone方法來(lái)實(shí)現(xiàn)克隆,不要著急。

3 模式講解#

3.1 認(rèn)識(shí)原型模式##

  1. 原型模式的功能

原型模式的功能實(shí)際上包含兩個(gè)方面:

一個(gè)是通過(guò)克隆來(lái)創(chuàng)建新的對(duì)象實(shí)例;

另一個(gè)是為克隆出來(lái)的新的對(duì)象實(shí)例復(fù)制原型實(shí)例屬性的值;

原型模式要實(shí)現(xiàn)的主要功能就是:通過(guò)克隆來(lái)創(chuàng)建新的對(duì)象實(shí)例。一般來(lái)講,新創(chuàng)建出來(lái)的實(shí)例的數(shù)據(jù)是和原型實(shí)例一樣的。但是具體如何實(shí)現(xiàn)克隆,需要由程序自行實(shí)現(xiàn),原型模式并沒(méi)有統(tǒng)一的要求和實(shí)現(xiàn)算法。

  1. 原型與new

原型模式從某種意義上說(shuō),就像是new操作,在前面的例子實(shí)現(xiàn)中,克隆方法就是使用new來(lái)實(shí)現(xiàn)的,但請(qǐng)注意,只是“類似于new”而不是“就是new”。

克隆方法和new操作最明顯的不同就在于:

new一個(gè)對(duì)象實(shí)例,一般屬性是沒(méi)有值的,或者是只有默認(rèn)值;如果是克隆得到的一個(gè)實(shí)例,通常屬性是有值的,屬性的值就是原型對(duì)象實(shí)例在克隆的時(shí)候,原型對(duì)象實(shí)例的屬性的值

  1. 原型實(shí)例和克隆的實(shí)例

原型實(shí)例和克隆出來(lái)的實(shí)例,本質(zhì)上是不同的實(shí)例,克隆完成后,它們之間是沒(méi)有關(guān)聯(lián)的,如果克隆完成后,克隆出來(lái)的實(shí)例的屬性的值發(fā)生了改變,是不會(huì)影響到原型實(shí)例的。下面寫個(gè)示例來(lái)測(cè)試一下,示例代碼如下:

public class Client {
    public static void main(String[] args) {
       //先創(chuàng)建原型實(shí)例
       OrderApi oa1 = new PersonalOrder();

       //設(shè)置原型實(shí)例的訂單數(shù)量的值
       oa1.setOrderProductNum(100);
       //為了簡(jiǎn)單,這里僅僅輸出數(shù)量
       System.out.println("這是第一次獲取的對(duì)象實(shí)例==="+oa1.getOrderProductNum());

       //通過(guò)克隆來(lái)獲取新的實(shí)例
       OrderApi oa2 = (OrderApi)oa1.cloneOrder();
       //修改它的數(shù)量
       oa2.setOrderProductNum(80);
       //輸出克隆出來(lái)的對(duì)象的值
       System.out.println("輸出克隆出來(lái)的實(shí)例==="+oa2.getOrderProductNum());

       //再次輸出原型實(shí)例的值
       System.out.println("再次輸出原型實(shí)例==="+oa1.getOrderProductNum());  
    }
}

運(yùn)行一下,看看結(jié)果:

這是第一次獲取的對(duì)象實(shí)例===100
輸出克隆出來(lái)的實(shí)例===80
再次輸出原型實(shí)例===100

仔細(xì)觀察上面的結(jié)果,會(huì)發(fā)現(xiàn)原型實(shí)例和克隆出來(lái)的實(shí)例是完全獨(dú)立的,也就是它們指向不同的內(nèi)存空間。因?yàn)榭寺〕鰜?lái)的實(shí)例的值已經(jīng)被改變了,而原型實(shí)例的值還是原來(lái)的值,并沒(méi)有變化,這就說(shuō)明兩個(gè)實(shí)例是對(duì)應(yīng)的不同內(nèi)存空間。

  1. 原型模式的調(diào)用順序示意圖
原型模式的調(diào)用順序示意圖

3.2 Java中的克隆方法##

在Java語(yǔ)言中已經(jīng)提供了clone方法,定義在Object類中。關(guān)于Java中clone方法的知識(shí),這里不去贅述,下面看看怎么使用Java里面的克隆方法來(lái)實(shí)現(xiàn)原型模式。

需要克隆功能的類,只需要實(shí)現(xiàn)java.lang.Cloneable接口,這個(gè)接口沒(méi)有需要實(shí)現(xiàn)的方法,是一個(gè)標(biāo)識(shí)接口。因此在前面的實(shí)現(xiàn)中,把訂單接口中的克隆方法去掉,現(xiàn)在直接實(shí)現(xiàn)Java中的接口就好了。新的訂單接口實(shí)現(xiàn),示例代碼如下:

public interface OrderApi {
    public int getOrderProductNum();
    public void setOrderProductNum(int num);
    // public OrderApi cloneOrder();
}

另外在具體的訂單實(shí)現(xiàn)對(duì)象里面,實(shí)現(xiàn)方式上會(huì)有一些改變,個(gè)人訂單和企業(yè)訂單的克隆實(shí)現(xiàn)是類似的,因此示范一個(gè)就好了,看看個(gè)人訂單的實(shí)現(xiàn)吧,示例代碼如下:

/**
 * 個(gè)人訂單對(duì)象,利用Java的Clone功能
 */
public class PersonalOrder implements Cloneable  , OrderApi {
    private String customerName;
    private String productId;
    private int orderProductNum = 0;
    public int getOrderProductNum() {
        return this.orderProductNum;
    }  
    public void setOrderProductNum(int num) {
        this.orderProductNum = num;
    }  
    public String getCustomerName() {
        return customerName;
    }
    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }
    public String getProductId() {
        return productId;
    }
    public void setProductId(String productId) {
        this.productId = productId;
    }
    public String toString(){
        return "本個(gè)人訂單的訂購(gòu)人是="+this.customerName+",訂購(gòu)產(chǎn)品是="+this.productId+",訂購(gòu)數(shù)量為="+this.orderProductNum;
    }
    // public OrderApi cloneOrder() {
        // 創(chuàng)建一個(gè)新的訂單,然后把本實(shí)例的數(shù)據(jù)復(fù)制過(guò)去
    //    PersonalOrder order = new PersonalOrder();
    //    order.setCustomerName(this.customerName);
    //    order.setProductId(this.productId);
    //    order.setOrderProductNum(this.orderProductNum);     
    //    return order;
    // }
    public Object clone() {
        //克隆方法的真正實(shí)現(xiàn),直接調(diào)用父類的克隆方法就可以了
        Object obj = null;
        try {
            obj = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return obj;
    }
}

看起來(lái),比完全由自己實(shí)現(xiàn)原型模式要稍稍簡(jiǎn)單點(diǎn),是否好用呢?還是測(cè)試一下,看看效果。客戶端跟上一個(gè)示例相比,作了兩點(diǎn)修改:

一個(gè)是原來(lái)的“OrderApi oa1 = new PersonalOrder();”這句話,要修改成:“PersonalOrder oa1 = new PersonalOrder();”。原因是現(xiàn)在的接口上并沒(méi)有克隆的方法,因此需要修改成原型的類型;

另外一個(gè)是“通過(guò)克隆來(lái)獲取新的實(shí)例”的實(shí)現(xiàn),需要修改成使用原型來(lái)調(diào)用在Object里面定義的clone()方法了,不再是調(diào)用原來(lái)的cloneOrder()了。

看看測(cè)試用的代碼,示例代碼如下:

public class Client {
    public static void main(String[] args) {
        //先創(chuàng)建原型實(shí)例
        PersonalOrder oa1 = new PersonalOrder();     
        //設(shè)置原型實(shí)例的訂單數(shù)量的值
        oa1.setOrderProductNum(100);
        System.out.println("這是第一次獲取的對(duì)象實(shí)例==="+oa1.getOrderProductNum());      
        //通過(guò)克隆來(lái)獲取新的實(shí)例
        PersonalOrder oa2 = (PersonalOrder)oa1.clone();
        oa2.setOrderProductNum(80);
        System.out.println("輸出克隆出來(lái)的實(shí)例==="+oa2.getOrderProductNum());     
        //再次輸出原型實(shí)例的值
        System.out.println("再次輸出原型實(shí)例==="+oa1.getOrderProductNum());  
    }
}

3.3 淺度克隆和深度克隆##

無(wú)論你是自己實(shí)現(xiàn)克隆方法,還是采用Java提供的克隆方法,都存在一個(gè)淺度克隆和深度克隆的問(wèn)題,那么什么是淺度克隆?什么是深度克隆呢?簡(jiǎn)單地解釋一下:

淺度克隆:只負(fù)責(zé)克隆按值傳遞的數(shù)據(jù)(比如:基本數(shù)據(jù)類型、String類型)

深度克隆:除了淺度克隆要克隆的值外,還負(fù)責(zé)克隆引用類型的數(shù)據(jù),基本上就是被克隆實(shí)例所有的屬性的數(shù)據(jù)都會(huì)被克隆出來(lái)。

深度克隆還有一個(gè)特點(diǎn),如果被克隆的對(duì)象里面的屬性數(shù)據(jù)是引用類型,也就是屬性的類型也是對(duì)象,那么需要一直遞歸的克隆下去。這也意味著,要想深度克隆成功,必須要整個(gè)克隆所涉及的對(duì)象都要正確實(shí)現(xiàn)克隆方法,如果其中有一個(gè)沒(méi)有正確實(shí)現(xiàn)克隆,那么就會(huì)導(dǎo)致克隆失敗。

在前面的例子中實(shí)現(xiàn)的克隆就是典型的淺度克隆,下面就來(lái)看看如何實(shí)現(xiàn)深度克隆。

  1. 自己實(shí)現(xiàn)原型的深度克隆

(1)要演示深度克隆,需要給訂單對(duì)象添加一個(gè)引用類型的屬性,這樣實(shí)現(xiàn)克隆過(guò)后,才能看出深度克隆的效果來(lái)。

那就定義一個(gè)產(chǎn)品對(duì)象,也需要讓它實(shí)現(xiàn)克隆的功能,產(chǎn)品對(duì)象實(shí)現(xiàn)的是一個(gè)淺度克隆。先來(lái)定義產(chǎn)品的原型接口,示例代碼如下:

/**
   * 聲明一個(gè)克隆產(chǎn)品自身的接口
   */
public interface ProductPrototype {
    /**
     * 克隆產(chǎn)品自身的方法
     * @return 一個(gè)從自身克隆出來(lái)的產(chǎn)品對(duì)象
     */
    public ProductPrototype cloneProduct();
}

接下來(lái)看看具體的產(chǎn)品對(duì)象實(shí)現(xiàn),示例代碼如下:

/**
   * 產(chǎn)品對(duì)象
   */
public class Product implements ProductPrototype{
    /**
     * 產(chǎn)品編號(hào)
     */
    private String productId;  
    /**
     * 產(chǎn)品名稱
     */
    private String name;
    public String getName() {
       return name;
    }
    public void setName(String name) {
       this.name = name;
    }
    public String getProductId() {
       return productId;
    }
    public void setProductId(String productId) {
       this.productId = productId;
    }
    public String toString(){
       return "產(chǎn)品編號(hào)="+this.productId+",產(chǎn)品名稱="+this.name;
    }
    public ProductPrototype cloneProduct() {
       //創(chuàng)建一個(gè)新的訂單,然后把本實(shí)例的數(shù)據(jù)復(fù)制過(guò)去
       Product product = new Product();
       product.setProductId(this.productId);
       product.setName(this.name);    
       return product;
    }
}

(2)訂單的具體實(shí)現(xiàn)上也需要改變一下,需要在其屬性上添加一個(gè)產(chǎn)品類型的屬性,然后也需要實(shí)現(xiàn)克隆方法,示例代碼如下:

public class PersonalOrder implements OrderApi{
    private String customerName;
    private int orderProductNum = 0;
    /**
     * 產(chǎn)品對(duì)象
     */
    private Product product = null;
    public int getOrderProductNum() {
       return this.orderProductNum;
    }  
    public void setOrderProductNum(int num) {
       this.orderProductNum = num;
    }  
    public String getCustomerName() {
       return customerName;
    }
    public void setCustomerName(String customerName) {
       this.customerName = customerName;
    }
    public Product getProduct() {
       return product;
    }
    public void setProduct(Product product) {
       this.product = product;
    }  
    public String toString(){
       //簡(jiǎn)單點(diǎn)輸出
       return "訂購(gòu)產(chǎn)品是="+this.product.getName()+",訂購(gòu)數(shù)量為="+this.orderProductNum;
    }
    public OrderApi cloneOrder() {
       //創(chuàng)建一個(gè)新的訂單,然后把本實(shí)例的數(shù)據(jù)復(fù)制過(guò)去
       PersonalOrder order = new PersonalOrder();
       order.setCustomerName(this.customerName);
       order.setOrderProductNum(this.orderProductNum);
       //對(duì)于對(duì)象類型的數(shù)據(jù),深度克隆的時(shí)候需要繼續(xù)調(diào)用這個(gè)對(duì)象的克隆方法
       order.setProduct((Product)this.product.cloneProduct());
       return order;
    }
}

(3)寫個(gè)客戶端來(lái)測(cè)試看看,是否深度克隆成功,示例代碼如下:

public class Client {
    public static void main(String[] args) {
       //先創(chuàng)建原型實(shí)例
       PersonalOrder oa1 = new PersonalOrder();
       //設(shè)置原型實(shí)例的值
       Product product = new Product();
       product.setName("產(chǎn)品1");
       oa1.setProduct(product);
       oa1.setOrderProductNum(100);

       System.out.println("這是第一次獲取的對(duì)象實(shí)例="+oa1);

       //通過(guò)克隆來(lái)獲取新的實(shí)例
       PersonalOrder oa2 = (PersonalOrder)oa1.cloneOrder();
       //修改它的值
       oa2.getProduct().setName("產(chǎn)品2");
       oa2.setOrderProductNum(80);
       //輸出克隆出來(lái)的對(duì)象的值
       System.out.println("輸出克隆出來(lái)的實(shí)例="+oa2);

       //再次輸出原型實(shí)例的值
       System.out.println("再次輸出原型實(shí)例="+oa1);
    }
}

(4)運(yùn)行結(jié)果如下,很明顯,我們自己做的深度克隆是成功的:

這是第一次獲取的對(duì)象實(shí)例=訂購(gòu)產(chǎn)品是=產(chǎn)品1,訂購(gòu)數(shù)量為=100
輸出克隆出來(lái)的實(shí)例=訂購(gòu)產(chǎn)品是=產(chǎn)品2,訂購(gòu)數(shù)量為=80
再次輸出原型實(shí)例=訂購(gòu)產(chǎn)品是=產(chǎn)品1,訂購(gòu)數(shù)量為=100

(5)小結(jié)

看來(lái)自己實(shí)現(xiàn)深度克隆也不是很復(fù)雜,但是比較麻煩,如果產(chǎn)品類里面又有屬性是引用類型的話,在產(chǎn)品類實(shí)現(xiàn)克隆方法的時(shí)候,又需要調(diào)用那個(gè)引用類型的克隆方法了,這樣一層一層調(diào)下去,如果中途有任何一個(gè)對(duì)象沒(méi)有正確實(shí)現(xiàn)深度克隆,那將會(huì)引起錯(cuò)誤,這也是深度克隆容易出錯(cuò)的原因。

  1. Java中的深度克隆

(1)產(chǎn)品類沒(méi)有太大的不同,主要是把實(shí)現(xiàn)的接口變成了Cloneable,這樣一來(lái),實(shí)現(xiàn)克隆的方法就不是cloneProduct,而是變成clone方法了;另外一個(gè)是克隆方法的實(shí)現(xiàn)變成了使用“super.clone();”了,示例代碼如下:

public class Product implements Cloneable{
    private String productId;  
    private String name;
    public String getName() {
       return name;
    }
    public void setName(String name) {
       this.name = name;
    }
    public String getProductId() {
       return productId;
    }
    public void setProductId(String productId) {
       this.productId = productId;
    }
    public String toString(){
       return "產(chǎn)品編號(hào)="+this.productId+",產(chǎn)品名稱="+this.name;
    }
    public Object clone() {
       Object obj = null;
       try {
           obj = super.clone();
       } catch (CloneNotSupportedException e) {
           e.printStackTrace();
       }
       return obj;
    }  
}

(2)具體的訂單實(shí)現(xiàn)類,除了改變接口外,更重要的是在實(shí)現(xiàn)clone方法的時(shí)候,除了調(diào)用“super.clone();”外,必須顯示的調(diào)用引用類型屬性的clone方法,也就是產(chǎn)品的clone方法,示例代碼如下:

public class PersonalOrder implements Cloneable , OrderApi{
    private String customerName;
    private Product product = null;
    private int orderProductNum = 0;  
    public int getOrderProductNum() {
       return this.orderProductNum;
    }  
    public void setOrderProductNum(int num) {
       this.orderProductNum = num;
    }  
    public String getCustomerName() {
       return customerName;
    }
    public void setCustomerName(String customerName) {
       this.customerName = customerName;
    }
    public Product getProduct() {
       return product;
    }
    public void setProduct(Product product) {
       this.product = product;
    }  
    public String toString(){
       //簡(jiǎn)單點(diǎn)輸出
       return "訂購(gòu)產(chǎn)品是="+this.product.getName()+",訂購(gòu)數(shù)量為="+this.orderProductNum;
    }
    public Object clone(){
       PersonalOrder obj=null;
       try {
          obj =(PersonalOrder)super.clone();
          //下面這一句話不可少
          obj.setProduct((Product)this.product.clone());
       } catch (CloneNotSupportedException e) {
          e.printStackTrace();
       }     
       return obj;
    }
}

(3)特別強(qiáng)調(diào):不可缺少“obj.setProduct((Product)this.product.clone());”這句話。為什么呢?

原因在于調(diào)用super.clone()方法的時(shí)候,Java是先開(kāi)辟一塊內(nèi)存的空間,然后把實(shí)例對(duì)象的值原樣拷貝過(guò)去,對(duì)于基本數(shù)據(jù)類型這樣做是沒(méi)有問(wèn)題的,而屬性product是一個(gè)引用類型,把值拷貝過(guò)去的意思就是把對(duì)應(yīng)的內(nèi)存地址拷貝過(guò)去了,也就是說(shuō)克隆后的對(duì)象實(shí)例的product和原型對(duì)象實(shí)例的product指向的是同一塊內(nèi)存空間,是同一個(gè)產(chǎn)品實(shí)例

因此要想正確的執(zhí)行深度拷貝,必須手工的對(duì)每一個(gè)引用類型的屬性進(jìn)行克隆,并重新設(shè)置,覆蓋掉super.clone()所拷貝的值

3.4 原型管理器##

如果一個(gè)系統(tǒng)中原型的數(shù)目不固定,比如系統(tǒng)中的原型可以被動(dòng)態(tài)的創(chuàng)建和銷毀,那么就需要在系統(tǒng)中維護(hù)一個(gè)當(dāng)前可用的原型的注冊(cè)表,這個(gè)注冊(cè)表就被稱為原型管理器。

其實(shí)如果把原型當(dāng)成一個(gè)資源的話,原型管理器就相當(dāng)于一個(gè)資源管理器,在系統(tǒng)開(kāi)始運(yùn)行的時(shí)候初始化,然后運(yùn)行期間可以動(dòng)態(tài)的添加資源和銷毀資源。從這個(gè)角度看,原型管理器就可以相當(dāng)于一個(gè)緩存資源的實(shí)現(xiàn),只不過(guò)里面緩存和管理的是原型實(shí)例而已。

有了原型管理器過(guò)后,一般情況下,除了向原型管理器里面添加原型對(duì)象的時(shí)候是通過(guò)new來(lái)創(chuàng)造的對(duì)象,其余時(shí)候都是通過(guò)向原型管理器來(lái)請(qǐng)求原型實(shí)例,然后通過(guò)克隆方法來(lái)獲取新的對(duì)象實(shí)例,這就可以實(shí)現(xiàn)動(dòng)態(tài)管理、或者動(dòng)態(tài)切換具體的實(shí)現(xiàn)對(duì)象實(shí)例。

還是通過(guò)示例來(lái)說(shuō)明,如何實(shí)現(xiàn)原型管理器。

  1. 先定義原型的接口,非常簡(jiǎn)單,除了克隆方法,提供一個(gè)名稱的屬性,示例代碼如下:
public interface Prototype {
    public Prototype clone();
    public String getName();
    public void setName(String name);
}
  1. 再來(lái)看看兩個(gè)具體的實(shí)現(xiàn),實(shí)現(xiàn)方式基本上是一樣的,分別看看。先看第一個(gè)原型的實(shí)現(xiàn),示例代碼如下:
public class ConcretePrototype1 implements Prototype {
    private String name;
    public String getName() {
       return name;
    }
    public void setName(String name) {
       this.name = name;
    }
    public Prototype clone() {
       ConcretePrototype1 prototype = new ConcretePrototype1();
       prototype.setName(this.name);
       return prototype;
    }
    public String toString(){
       return "Now in Prototype1,name="+name;
    }
}

再看看第二個(gè)原型的實(shí)現(xiàn),示例代碼如下:

public class ConcretePrototype2 implements Prototype {
    private String name;
    public String getName() {
       return name;
    }
    public void setName(String name) {
       this.name = name;
    }
    public Prototype clone() {
       ConcretePrototype2 prototype = new ConcretePrototype2();
       prototype.setName(this.name);
       return prototype;
    }  
    public String toString(){
       return "Now in Prototype2,name="+name;
    }
}
  1. 接下來(lái)看看原型管理器的實(shí)現(xiàn)示意,示例代碼如下:
/**
 * 原型管理器
 */
public class PrototypeManager {
    /**
     * 用來(lái)記錄原型的編號(hào)和原型實(shí)例的對(duì)應(yīng)關(guān)系
     */
    private static Map<String,Prototype> map = new HashMap<String,Prototype>();
    /**
     * 私有化構(gòu)造方法,避免外部無(wú)謂的創(chuàng)建實(shí)例
     */
    private PrototypeManager(){
       //
    }
    /**
     * 向原型管理器里面添加或是修改某個(gè)原型注冊(cè)
     * @param prototypeId 原型編號(hào)
     * @param prototype 原型實(shí)例
     */
    public synchronized static void setPrototype(String prototypeId,Prototype prototype){
       map.put(prototypeId, prototype);
    }
    /**
     * 從原型管理器里面刪除某個(gè)原型注冊(cè)
     * @param prototypeId 原型編號(hào)
     */
    public synchronized static void removePrototype(String prototypeId){
       map.remove(prototypeId);
    }
    /**
     * 獲取某個(gè)原型編號(hào)對(duì)應(yīng)的原型實(shí)例
     * @param prototypeId 原型編號(hào)
     * @return 原型編號(hào)對(duì)應(yīng)的原型實(shí)例
     * @throws Exception 如果原型編號(hào)對(duì)應(yīng)的原型實(shí)例不存在,報(bào)出例外
     */
    public synchronized static Prototype getPrototype(String prototypeId)throws Exception{
       Prototype prototype = map.get(prototypeId);
       if(prototype == null){
           throw new Exception("您希望獲取的原型還沒(méi)有注冊(cè)或已被銷毀");
       }
       return prototype;
    }
}

大家會(huì)發(fā)現(xiàn),原型管理器是類似一個(gè)工具類的實(shí)現(xiàn)方式,而且對(duì)外的幾個(gè)方法都是加了同步的,這主要是因?yàn)槿绻诙嗑€程環(huán)境下使用這個(gè)原型管理器的話,那個(gè)map屬性很明顯就成了大家競(jìng)爭(zhēng)的資源,因此需要加上同步

  1. 接下來(lái)看看客戶端,如何使用這個(gè)原型管理器,示例代碼如下:
public class Client {
    public static void main(String[] args) {
       try {
           // 初始化原型管理器
           Prototype p1 = new ConcretePrototype1();
           PrototypeManager.setPrototype("Prototype1", p1);

           // 獲取原型來(lái)創(chuàng)建對(duì)象
           Prototype p3 = PrototypeManager.getPrototype("Prototype1").clone();
           p3.setName("張三");
           System.out.println("第一個(gè)實(shí)例:" + p3);

           // 有人動(dòng)態(tài)的切換了實(shí)現(xiàn)
           Prototype p2 = new ConcretePrototype2();
           PrototypeManager.setPrototype("Prototype1", p2);

           // 重新獲取原型來(lái)創(chuàng)建對(duì)象
           Prototype p4 = PrototypeManager.getPrototype("Prototype1").clone();
           p4.setName("李四");
           System.out.println("第二個(gè)實(shí)例:" + p4);

           // 有人注銷了這個(gè)原型
           PrototypeManager.removePrototype("Prototype1");

           // 再次獲取原型來(lái)創(chuàng)建對(duì)象
           Prototype p5 = PrototypeManager.getPrototype("Prototype1").clone();
           p5.setName("王五");
           System.out.println("第三個(gè)實(shí)例:" + p5);
       } catch (Exception err) {
           System.err.println(err.getMessage());
       }
    }
}

運(yùn)行一下,看看結(jié)果,結(jié)果示例如下:

第一個(gè)實(shí)例:Now in Prototype1,name=張三
第二個(gè)實(shí)例:Now in Prototype2,name=李四
您希望獲取的原型還沒(méi)有注冊(cè)或已被銷毀

3.5 原型模式的優(yōu)缺點(diǎn)##

  1. 對(duì)客戶端隱藏具體的實(shí)現(xiàn)類型

原型模式的客戶端,只知道原型接口的類型,并不知道具體的實(shí)現(xiàn)類型,從而減少了客戶端對(duì)這些具體實(shí)現(xiàn)類型的依賴。

  1. 在運(yùn)行時(shí)動(dòng)態(tài)改變具體的實(shí)現(xiàn)類型

原型模式可以在運(yùn)行期間,由客戶來(lái)注冊(cè)符合原型接口的實(shí)現(xiàn)類型,也可以動(dòng)態(tài)的改變具體的實(shí)現(xiàn)類型,看起來(lái)接口沒(méi)有任何變化,但其實(shí)運(yùn)行的已經(jīng)是另外一個(gè)類實(shí)例了。因?yàn)榭寺∫粋€(gè)原型就類似于實(shí)例化一個(gè)類。

  1. 深度克隆方法實(shí)現(xiàn)會(huì)比較困難

原型模式最大的缺點(diǎn)就在于每個(gè)原型的子類都必須實(shí)現(xiàn)clone的操作,尤其在包含引用類型的對(duì)象時(shí),clone方法會(huì)比較麻煩,必須要能夠遞歸的讓所有的相關(guān)對(duì)象都要正確的實(shí)現(xiàn)克隆。

3.6 思考原型模式##

  1. 原型模式的本質(zhì)

原型模式的本質(zhì):克隆生成對(duì)象。

克隆是手段,目的還是生成新的對(duì)象實(shí)例。正是因?yàn)樵偷哪康氖菫榱松尚碌膶?duì)象實(shí)例,原型模式通常是被歸類為創(chuàng)建型的模式

原型模式也可以用來(lái)解決“只知接口而不知實(shí)現(xiàn)的問(wèn)題”,使用原型模式,可以出現(xiàn)一種獨(dú)特的“接口造接口”的景象,這在面向接口編程中很有用。同樣的功能也可以考慮使用工廠來(lái)實(shí)現(xiàn)。

另外,原型模式的重心還是在創(chuàng)建新的對(duì)象實(shí)例,至于創(chuàng)建出來(lái)的對(duì)象,其屬性的值是否一定要和原型對(duì)象屬性的值完全一樣,這個(gè)并沒(méi)有強(qiáng)制規(guī)定,只不過(guò)在目前大多數(shù)實(shí)現(xiàn)中,克隆出來(lái)的對(duì)象和原型對(duì)象的屬性值是一樣的。

也就是說(shuō),可以通過(guò)克隆來(lái)創(chuàng)造值不一樣的實(shí)例,但是對(duì)象類型必須一樣。可以有部分甚至是全部的屬性的值不一樣,可以有選擇性的克隆,就當(dāng)是標(biāo)準(zhǔn)原型模式的一個(gè)變形使用吧。

  1. 何時(shí)選用原型模式

建議在如下情況中,選用原型模式:

如果一個(gè)系統(tǒng)想要獨(dú)立于它想要使用的對(duì)象時(shí),可以使用原型模式,讓系統(tǒng)只面向接口編程,在系統(tǒng)需要新的對(duì)象的時(shí)候,可以通過(guò)克隆原型來(lái)得到;

如果需要實(shí)例化的類是在運(yùn)行時(shí)刻動(dòng)態(tài)指定時(shí),可以使用原型模式,通過(guò)克隆原型來(lái)得到需要的實(shí)例;

3.7 相關(guān)模式##

  1. 原型模式和抽象工廠模式

功能上有些相似,都是用來(lái)獲取一個(gè)新的對(duì)象實(shí)例的。

不同之處在于,原型模式的著眼點(diǎn)是在如何創(chuàng)造出實(shí)例對(duì)象來(lái),最后選擇的方案是通過(guò)克隆;而抽象工廠模式的著眼點(diǎn)則在于如何來(lái)創(chuàng)造產(chǎn)品簇,至于具體如何創(chuàng)建出產(chǎn)品簇中的每個(gè)對(duì)象實(shí)例,抽象工廠模式不是很關(guān)注

正是因?yàn)樗鼈兊年P(guān)注點(diǎn)不一樣,所以它們也可以配合使用,比如在抽象工廠模式里面,具體創(chuàng)建每一種產(chǎn)品的時(shí)候就可以使用該種產(chǎn)品的原型,也就是抽象工廠管產(chǎn)品簇,具體的每種產(chǎn)品怎么創(chuàng)建則可以選擇原型模式。

  1. 原型模式和生成器模式

這兩種模式可以配合使用。

生成器模式關(guān)注的是構(gòu)建的過(guò)程,而在構(gòu)建的過(guò)程中,很可能需要某個(gè)部件的實(shí)例,那么很自然地就可以應(yīng)用上原型模式,通過(guò)原型模式來(lái)得到部件的實(shí)例。

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

推薦閱讀更多精彩內(nèi)容