研磨设计模式 之 原型模式(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的那一段。有朋友可能会问,这样实现有何不可吗?
这样的实现有如下几个问题:
- 既然想要实现通用的订单处理,那么对于订单处理的实现对象,是不应该知道订单的具体实现的,更不应该依赖订单的具体实现。但是上面的实现中,很明显订单处理的对象依赖了订单的具体实现对象。
- 这种实现方式另外一个问题就是:难以扩展新的订单类型。假如现在要加入一个大客户专用订单的类型,那么就需要修改订单处理的对象,要在里面添加对新的订单类型的支持,这算哪门子的通用处理。
因此,上面的实现是不太好的,把上面的问题再抽象描述一下:已经有了某个对象实例后,如何能够快速简单地创建出更多的这种对象?
比如上面的问题,就是已经有了订单接口类型的对象实例,然后在方法中需要创建出更多的这种对象。怎么解决呢?
---------------------------------------------------------------------------