用來解決上述問題的一個合理的解決方案就是原型模式。那麼什麼是原型模式呢?
(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();”,就用一個訂單的原型實例來指定了對象的種類,然後通過克隆這個原型實例來創建出了一個新的對象實例。