研磨設計模式 之 原型模式(Prototype)1 ——跟著cc學設計系列
9.1 場景問題
9.1.1 訂單處理系統
考慮這樣一個實際應用:訂單處理系統。
現在有一個訂單處理的系統,裡面有個儲存訂單的業務功能,在這個業務功能裡面,客戶有這麼一個需求:每當訂單的預定產品數量超過1000的時候,就需要把訂單拆成兩份訂單來儲存,如果拆成兩份訂單後,還是超過1000,那就繼續拆分,直到每份訂單的預定產品數量不超過1000。至於為什麼要拆分,原因是好進行訂單的後續處理,後續是由人工來處理,每個人工工作小組的處理能力上限是1000。
根據業務,目前的訂單型別被分成兩種:一種是個人訂單,一種是公司訂單。現在想要實現一個通用的訂單處理系統,也就是說,不管具體是什麼型別的訂單,都要能夠正常的處理。
該怎麼實現呢?
9.1.2 不用模式的解決方案
來分析上面要求實現的功能,有朋友會想,這很簡單嘛,一共就一個功能,沒什麼困難的,真的是這樣嗎?先來嘗試著實現看看。
(1)定義訂單介面
首先,要想實現通用的訂單處理,而不關心具體的訂單型別,那麼很明顯,訂單處理的物件應該面向一個訂單的介面或是一個通用的訂單物件來程式設計,這裡就選用面向訂單介面來處理吧,先把這個訂單介面定義出來,示例程式碼如下:
/** * 訂單的介面 */ public interface OrderApi { /** * 獲取訂單產品數量 * @return 訂單中產品數量 */ public int getOrderProductNum(); /** * 設定訂單產品數量 * @param num 訂單產品數量 */ public void setOrderProductNum(int num); } |
(2)既然定義好了訂單的介面,那麼接下來把各種型別的訂單實現出來,先看看個人的訂單實現,示例程式碼如下:
/** * 個人訂單物件 */ 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 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; } } |
有些朋友看到這裡,可能會有這樣的疑問:看上去上面兩種型別的訂單物件,僅僅是一個數據封裝的物件,而且還有一些資料是相同的,為何不抽出一個父類來,把共同的資料定義在父類裡面呢?
這裡有兩個考慮,一個是:這裡僅僅是一個示意,實際情況遠比這複雜,實際開發中不會僅僅是資料封裝物件這麼簡單。另外一個是:為了後續示例的重點突出,這裡要學習的是原型模式,因此就沒有去抽取父類,以免物件層級過多,影響主題的展示。
(3)實現好了訂單物件,接下來看看如何實現通用的訂單處理,先把訂單處理的物件大概定義出來,示例程式碼如下:
/** * 處理訂單的業務物件 */ public class OrderBusiness { /** * 建立訂單的方法 * @param order 訂單的介面物件 */ public void saveOrder(OrderApi order){ //等待具體實現 } } |
現在的中心任務就是要來實現這個saveOrder的方法,傳入的引數是一個訂單的介面物件,這個方法要實現的功能:根據業務要求,當訂單的預定產品數量超過1000的時候,就需要把訂單拆成兩份訂單。
那好,來嘗試著實現一下,因為預定的數量可能會很大,因此採用一個while迴圈來處理,直到拆分後訂單的數量不超過1000,先把實現的思路寫出來,示例程式碼如下:
public class OrderBusiness { public void saveOrder(OrderApi order){ //1:判斷當前的預定產品數量是否大於1000 while(order.getOrderProductNum() > 1000){ //2:如果大於,還需要繼續拆分 //2.1再新建一份訂單,跟傳入的訂單除了數量不一樣外,其它都相同 OrderApi newOrder = null; } } } |
大家會發現,才剛寫到第二步就寫不下去了,為什麼呢?因為現在判斷需要拆分訂單,也就是需要新建一個訂單物件,可是訂單處理物件面對的是訂單的介面,它根本就不知道現在訂單具體的型別,也不知道具體的訂單實現,它無法創建出新的訂單物件來,也就無法實現訂單拆分的功能了。
(4)一個簡單的解決辦法
有朋友提供了這麼一個解決的思路,他說:不就是在saveOrder方法裡面不知道具體的型別,從而導致無法建立物件嗎?很簡單,使用instanceof來判斷不就可以了,他還給出了他的實現示意,示意程式碼如下:
public class OrderBusiness { public void saveOrder(OrderApi order){ while(order.getOrderProductNum() > 1000) //定義一個表示被拆分出來的新訂單物件 OrderApi newOrder = null; if (order instanceof PersonalOrder){ //建立相應的訂單物件 PersonalOrder p2 = new PersonalOrder(); //然後進行賦值等,省略了 //然後再設定給newOrder newOrder = p2; }else if(order instanceof EnterpriseOrder){ //建立相應的訂單物件 EnterpriseOrder e2 = new EnterpriseOrder(); //然後進行賦值等,省略了 //然後再設定給newOrder newOrder = e2; } //然後進行拆分和其它業務功能處理,省略了 } } } |
好像能解決問題,對吧。那我們就來按照他提供的思路,把這個通用的訂單處理物件實現出來,示例程式碼如下:
/** * 處理訂單的業務物件 */ public class OrderBusiness { /** * 建立訂單的方法 * @param order 訂單的介面物件 */ public void saveOrder(OrderApi order){ //根據業務要求,當訂單預定產品數量超過1000時,就要把訂單拆成兩份訂單 //當然如果要做好,這裡的1000應該做成常量,這麼做是為了演示簡單 //1:判斷當前的預定產品數量是否大於1000 while(order.getOrderProductNum() > 1000){ //2:如果大於,還需要繼續拆分 //2.1再新建一份訂單,跟傳入的訂單除了數量不一樣外,其它都相同 OrderApi newOrder = null; if(order instanceof PersonalOrder){ //建立相應的新的訂單物件 PersonalOrder p2 = new PersonalOrder(); //然後進行賦值,但是產品數量為1000 PersonalOrder p1 = (PersonalOrder)order; p2.setCustomerName(p1.getCustomerName()); p2.setProductId(p1.getProductId()); p2.setOrderProductNum(1000); //然後再設定給newOrder newOrder = p2; }else if(order instanceof EnterpriseOrder){ //建立相應的訂單物件 EnterpriseOrder e2 = new EnterpriseOrder(); //然後進行賦值,但是產品數量為1000 EnterpriseOrder e1 = (EnterpriseOrder)order; e2.setEnterpriseName(e1.getEnterpriseName()); e2.setProductId(e1.getProductId()); e2.setOrderProductNum(1000); //然後再設定給newOrder newOrder = e2; } //2.2原來的訂單保留,把數量設定成減少1000 order.setOrderProductNum( order.getOrderProductNum()-1000); //然後是業務功能處理,省略了,列印輸出,看一下 System.out .println("拆分生成訂單=="+newOrder); } //3:不超過1000,那就直接業務功能處理,省略了,列印輸出,看一下 System.out .println("訂單=="+order); } } |
(5)寫個客戶端來測試一下,示例程式碼如下:
public class OrderClient { public static void main(String[] args) { //建立訂單物件,這裡為了演示簡單,直接new了 PersonalOrder op = new PersonalOrder(); //設定訂單資料 op.setOrderProductNum(2925); op.setCustomerName("張三"); op.setProductId("P0001"); //這裡獲取業務處理的類,也直接new了,為了簡單,連業務介面都沒有做 OrderBusiness ob = new OrderBusiness(); //呼叫業務來儲存訂單物件 ob.saveOrder(op); } } |
執行結果如下:
拆分生成訂單==本個人訂單的訂購人是=張三,訂購產品是=P0001,訂購數量為=1000 拆分生成訂單==本個人訂單的訂購人是=張三,訂購產品是=P0001,訂購數量為=1000 訂單==本個人訂單的訂購人是=張三,訂購產品是=P0001,訂購數量為=925 |
根據訂單中訂購產品的數量,一份訂單被拆分成了三份。
同樣的,你還可以傳入企業訂單,看看是否能正常滿足功能要求。
9.1.3 有何問題
看起來,上面的實現確實不難,好像也能夠通用的進行訂單處理,而不需要關心訂單的型別和具體實現這樣的功能。
仔細想想,真的沒有關心訂單的型別和具體實現嗎?答案是“否定的”。
事實上,在實現訂單處理的時候,上面的實現是按照訂單的型別和具體實現來處理的,就是instanceof的那一段。有朋友可能會問,這樣實現有何不可嗎?
這樣的實現有如下幾個問題:
- 既然想要實現通用的訂單處理,那麼對於訂單處理的實現物件,是不應該知道訂單的具體實現的,更不應該依賴訂單的具體實現。但是上面的實現中,很明顯訂單處理的物件依賴了訂單的具體實現物件。
- 這種實現方式另外一個問題就是:難以擴充套件新的訂單型別。假如現在要加入一個大客戶專用訂單的型別,那麼就需要修改訂單處理的物件,要在裡面新增對新的訂單型別的支援,這算哪門子的通用處理。
因此,上面的實現是不太好的,把上面的問題再抽象描述一下:已經有了某個物件例項後,如何能夠快速簡單地創建出更多的這種物件?
比如上面的問題,就是已經有了訂單介面型別的物件例項,然後在方法中需要創建出更多的這種物件。怎麼解決呢?
---------------------------------------------------------------------------