1. 程式人生 > >研磨設計模式 之 原型模式(Prototype)2 ——跟著cc學設計系列

研磨設計模式 之 原型模式(Prototype)2 ——跟著cc學設計系列


9.2  解決方案

9.2.1  原型模式來解決

用來解決上述問題的一個合理的解決方案就是原型模式。那麼什麼是原型模式呢?

(1 )原型模式定義

 

(2 )應用原型模式來解決的思路

仔細分析上面的問題,在saveOrder方法裡面,已經有了訂單介面型別的物件例項,是從外部傳入的,但是這裡只是知道這個例項物件的種類是訂單的介面型別,並不知道其具體的實現型別,也就是不知道它到底是個人訂單還是企業訂單,但是現在需要在這個方法裡面建立一個這樣的訂單物件,看起來就像是要通過介面來建立物件一樣。

原型模式就可以解決這樣的問題,原型模式會要求物件實現一個可以“克隆”自身的介面,這樣就可以通過拷貝或者是克隆一個例項物件本身,來建立一個新的例項。如果把這個方法定義在介面上,看起來就像是通過介面來建立了新的介面物件。

這樣一來,通過原型例項建立新的物件,就不再需要關心這個例項本身的型別,也不關心它的具體實現,只要它實現了克隆自身的方法,就可以通過這個方法來獲取新的物件,而無須再去通過new來建立。

9.2.1  模式結構和說明

原型模式的結構如圖9.1所示:

 

圖9.1  原型模式結構示意圖

Prototype

宣告一個克隆自身的介面,用來約束想要克隆自己的類,要求它們都要實現這裡定義的克隆方法。

ConcretePrototype

實現Prototype介面的類,這些類真正實現了克隆自身的功能。

Client

使用原型的客戶端,首先要獲取到原型例項物件,然後通過原型例項克隆自身來建立新的物件例項。

9.2.3  原型模式示例程式碼

(1)先來看看原型介面的定義,示例程式碼如下:

/**

 * 宣告一個克隆自身的介面

 */

public interface Prototype {

    /**

     * 克隆自身的方法

     * @return 一個從自身克隆出來的物件

     */

    public Prototype clone();

}

(2)接下來看看具體的原型實現物件,示例程式碼如下:

/**

 * 克隆的具體實現物件

 */

public class ConcretePrototype1 implements Prototype {

    public Prototype clone() {

       //最簡單的克隆,新建一個自身物件,由於沒有屬性,就不去複製值了

       Prototype prototype = new ConcretePrototype1();

       return prototype;

    }

}

/**

 * 克隆的具體實現物件

 */

public class ConcretePrototype2 implements Prototype {

    public Prototype clone() {

       //最簡單的克隆,新建一個自身物件,由於沒有屬性,就不去複製值了

       Prototype prototype = new ConcretePrototype2();

       return prototype;

    }

}

       為了跟上面原型模式的結構示意圖保持一致,因此這兩個具體的原型實現物件,都沒有定義屬性。事實上,在實際使用原型模式的應用中,原型物件多是有屬性的,克隆原型的時候也是需要克隆原型物件的屬性的,特此說明一下。

(3)再看看使用原型的客戶端,示例程式碼如下:

/**

 * 使用原型的客戶端

 */

public class Client {

    /**

     * 持有需要使用的原型介面物件

     */

    private Prototype prototype;

    /**

     * 構造方法,傳入需要使用的原型介面物件

     * @param prototype 需要使用的原型介面物件

     */

    public Client(Prototype prototype){

       this.prototype = prototype;

    }

    /**

     * 示意方法,執行某個功能操作

     */

    public void operation(){

       //會需要建立原型介面的物件

       Prototype newPrototype = prototype.clone();

    }

}

9.2.4  使用原型模式重寫示例

       要使用原型模式來重寫示例,先要在訂單的介面上定義出克隆的介面,然後要求各個具體的訂單物件克隆自身,這樣就可以解決:在訂單處理物件裡面通過訂單介面來建立新的訂單物件的問題。

       使用原型模式來重寫示例的結構如圖9.2所示:

 

圖9.2  使用原型模式來重寫示例的結構示意圖

下面一起來看看具體的實現。

(1 )複製誰和誰來複制的問題

有了一個物件例項,要快速的建立跟它一樣的例項,最簡單的辦法就是複製?這裡又有兩個小的問題

  • 複製誰呢?當然是複製這個物件例項,複製例項的意思是連帶著資料一起復制。
  • 誰來複制呢?應該讓這個類的例項自己來複制,自己複製自己。

       可是每個物件不會那麼聽話,自己去實現複製自己的。於是原型模式決定對這些物件實行強制要求,給這些物件定義一個介面,在接口裡面定義一個方法,這個方法用來要求每個物件實現自己複製自己。

由於現在存在訂單的介面,因此就把這個要求克隆自身的方法定義在訂單的接口裡面,示例程式碼如下:

/**

 * 訂單的介面,聲明瞭可以克隆自身的方法

 */

public interface OrderApi {

    public int getOrderProductNum();

    public void setOrderProductNum(int num);

    /**

     * 克隆方法

     * @return 訂單原型的例項

     */

    public  OrderApi cloneOrder();

}

(2 )如何克隆

定義好了克隆的介面,那麼在訂單的實現類裡面,就得讓它實現這個介面,並具體的實現這個克隆方法,新的問題出來了,如何實現克隆呢?

       很簡單,只要先new一個自己物件的例項,然後把自己例項中的資料取出來,設定到新的物件例項中去,不就可以完成例項的複製了嘛,複製的結果就是有了一個跟自身一模一樣的例項。

       有的朋友可能會說:不用那麼費勁吧,直接返回本例項不就可以了?如下:

public Object clone(){

    return this;

}

請注意了,這是一種典型的錯誤 ,這麼做,每次克隆,客戶端獲取的其實都是同一個例項,都是指向同一個記憶體空間的,對克隆出來的物件例項的修改會影響到原型物件例項。

       那麼應該怎麼克隆呢,最基本的做法就是新建一個類例項,然後把所有屬性的值複製到新的例項中,先看看個人訂單物件的實現,示例程式碼如下:

/**

 * 個人訂單物件

 */

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 "本個人訂單的訂購人是="+this.customerName

+",訂購產品是="+this.productId+",訂購數量為="

+this.orderProductNum;

    }  

    public OrderApi cloneOrder() {

       //建立一個新的訂單,然後把本例項的資料複製過去

       PersonalOrder order = new PersonalOrder();

       order.setCustomerName(this.customerName);

       order.setProductId(this.productId);

       order.setOrderProductNum(this.orderProductNum);

       return order;

    }

}

       接下來看看企業訂單的具體實現,示例程式碼如下:

/**

 * 企業訂單物件

 */

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 "本企業訂單的訂購企業是="+this.enterpriseName

+",訂購產品是="+this.productId+",訂購數量為="

+this.orderProductNum;

    }

    public OrderApi cloneOrder() {

       //建立一個新的訂單,然後把本例項的資料複製過去

       EnterpriseOrder order = new EnterpriseOrder();

       order.setEnterpriseName(this.enterpriseName);

       order.setProductId(this.productId);

       order.setOrderProductNum(this.orderProductNum);

       return order;

    }  

}

(3 )使用克隆方法

這裡使用訂單介面的克隆方法的,是訂單的處理物件,也就是說,訂單的處理物件就相當於原型模式結構中的Client。

當然,客戶端在呼叫clone方法之前,還需要先獲得相應的例項物件,有了例項物件,才能呼叫該例項物件的clone方法。

這裡使用克隆方法的時候,跟標準的原型實現有一些不同,在標準的原型實現的示例程式碼裡面,客戶端是持有需要克隆的物件,而這裡變化成了通過方法傳入需要使用克隆的物件,這點大家注意一下。示例程式碼如下:

public class OrderBusiness {

    /**

     * 建立訂單的方法

     * @param order 訂單的介面物件

     */

    public void saveOrder(OrderApi order){

       //1:判斷當前的預定產品數量是否大於1000

       while(order.getOrderProductNum() > 1000){

           //2:如果大於,還需要繼續拆分

           //2.1再新建一份訂單,跟傳入的訂單除了數量不一樣外,其它都相同

           OrderApi newOrder = order.cloneOrder();

           //然後進行賦值,產品數量為1000

           newOrder.setOrderProductNum(1000);

           //2.2原來的訂單保留,把數量設定成減少1000

           order.setOrderProductNum(

order.getOrderProductNum()-1000);

           //然後是業務功能處理,省略了,列印輸出,看一下

           System.out .println("拆分生成訂單=="+newOrder);

       }     

       //3:不超過,那就直接業務功能處理,省略了,列印輸出,看一下

       System.out .println("訂單=="+order);

    }

}

(4)客戶端的測試程式碼,跟前面的示例是完全一樣的,這裡就不去贅述了,去執行一下,看看執行的效果,享受一下克隆的樂趣。

在上面的例子中,在訂單處理物件的儲存訂單方法裡面的這句話“OrderApi newOrder = order.cloneOrder();”,就用一個訂單的原型例項來指定了物件的種類,然後通過克隆這個原型例項來創建出了一個新的物件例項。

9.2.5  小提示

看到這裡,可能有些朋友會認為:Java的Object裡面本身就有clone方法,還用搞得這麼麻煩嗎?

雖然Java裡面有clone方法,上面這麼做還是很有意義的,可以讓我們更好的、更完整的體會原型這個設計模式。當然,後面會講述如何使用Java裡面的clone方法來實現克隆,不要著急。