考虑这样一个实际用例:订单处理系统。
现有一个订单处理的系统,里面有一个保存订单的业务功能。在这个业务功能中,客户有这样一个需求:每当订单的预定产品数量超过1000,就把订单拆成两份保存。如果拆成两份订单后,它还超过1000,那就继续拆分,直到订单订单少于1000为止。至于为什么要拆分的话,可能就是后续工作人员只能处理少于1000产品数的订单,这里不要太纠结。
分析一下功能需求,其实这种应用场景并不算难,一共就一个功能,多加几个if语句,但是真的那么简单吗?下面我们查实尝试写一写看看。
1.定义订单接口。
首先,要想实现通用的订单处理,而不关心具体的订单类型,那么很明显,订单处理的对象应该面向一个订单的接口或是一个通用的订单对象来编程,这里就选用面向订单的接口来处理。先把这个订单接口定义出来。
/**
* 获取订单接口
* @author IT191015
*/
public interface OrderApi {
/**
* 获取订单接口
* @return
*/
public int getOrderProductNum();
/*
*设置订单产品数量接口
*/
public void setOrderProductNum(int num);
}
2.既然定义好了订单的接口,那么接下来把各种类型的订单实现出来。个人订单代码如下:
/*
*个人订单
*/
public class PersonalOrder implements OrderApi {
private String customerName;//订购人员姓名
private String productId;//产品id
private int orderProductNum = 0;
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;
}
@Override
public int getOrderProductNum() {
return this.orderProductNum;
}
@Override
public void setOrderProductNum(int num) {
this.orderProductNum = num;
}
@Override
public String toString() {
return "PersonalOrder [customerName=" + customerName + ", productId=" + productId + ", orderProductNum="
+ orderProductNum + "]";
}
}
3.企业订单实现,具体代码如下:
/**
* 企业订单
* @author IT191015
*/
public class EnterpriseOrder implements OrderApi {
private String enterpriseName;//名称
private String productId;//产品id
private int orderProductNum = 0;//数量
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;
}
@Override
public int getOrderProductNum() {
return this.orderProductNum;
}
@Override
public void setOrderProductNum(int num) {
this.orderProductNum = num;
}
@Override
public String toString() {
return "EnterpriseOrder [enterpriseName=" + enterpriseName + ", productId=" + productId + ", orderProductNum="
+ orderProductNum + "]";
}
}
4.一个简单的解决办法。
在saveOrder方法里面不知道具体的类型,从而导致无法创建对象吗?很简单,使用instanceof来判断不就行了。具体的实现代码如下:
public class OrderBusiness {
/**
* 创建订单的方法
* @param order 订单的接口对象
*/
public void saveOrder(OrderApi order) {
//1:判断当前的预判产品数量是否大于1000
while(order.getOrderProductNum() > 1000) {
//2:如果大于1000,继续拆分
//2.1再新建一份订单,跟传入的订单除了数量不一样外,其他都相同
OrderApi newOrder = null;
if(order instanceof PersonalOrder) {
//创建相应的订单对象
PersonalOrder p2 = new PersonalOrder();
PersonalOrder p1 = (PersonalOrder)order;
//赋值
p2.setCustomerName(p1.getCustomerName());
p2.setOrderProductNum(1000);
p2.setProductId(p1.getProductId());
//再设置newOrde
newOrder = p2;
}
else if (order instanceof EnterpriseOrder) {
//创建相应的订单对象
EnterpriseOrder e2 = new EnterpriseOrder();
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);
//3:不超过1000,那就直接业务功能处理,省略了,打印输出
System.out.println("订单 == " + order);
}
}
}
5.用客户端测试一下
/**
* 客户测试
* @author IT191015
*/
public class OrderClient {
public static void main(String[] args) {
// 创建订单对象,这里为了演示简单,直接就new
PersonalOrder op = new PersonalOrder();
//设置订单数据
op.setCustomerName("wanggbjclohjbio");
op.setOrderProductNum(4656);
op.setProductId("rifeng110");
//获取业务处理类
OrderBusiness ob = new OrderBusiness();
ob.saveOrder(op);
}
}
这样是实现起来好像没有什么难度,基本的功能实现是没有问题的,而不需要关心订单的类型和具体实现这样的功能。
实际上,在订单处理的时候,上面的实现是按照订单的类型和具体实现来处理的,就是instanceof的那段代码。还会有人问,这样实现有什么不妥吗?
我就列一列问题吧。
1.既然想要实现通用的订单处理,那么对于订单处理的实现对象,是不应该知道订单的具体实现的,更不应该依赖订单的具体实现。但是上面的实现中,很明显订单处理的对象依赖了订单的具体实现对象。
2.这种实现方式另外一个问题就是:难以扩展新的订单类型。假如添加了一个订单类型,那就修改订单处理功能中的代码,也就违反了开闭原则。
1.原型模式的定义
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
这个定义应该很好理解,不过理解不了可以先看看例子完了以后再回来读读概念,我解决这样肯定会效率更高。
2.应用原型模式解决问题的思路
分析一下上面的代码,在saveOrder方法里面 ,已经有了订单接口类型的对象实例,是从外部传入的,但是这里只是知道这个实例对象的种类是订单的接口类型,并不知道其具体实现类型,也就是不知道具体的实现类型,也就是不知道到底是个人订单还是企业订单,但是现在需要在这个方法里面创建一个这样的订单对象,看起来就像是要通过接口来创建对象一样。
原型模式会要求实现一个可以“克隆”自身的接口,这样就可以通过 拷贝或者克隆一个实例对象本身来创建一个实例。如果把这个方法定义在接口上,看起来就像是通过接口来创建了新的接口对象。
Prototype:声明一个克隆自身的接口,用来约束想要克隆自己的类,要求它们都要实现这里定义的克隆方法。
ConretePrototype:实现Prototype接口的类,这些类真正实现了克隆自身的功能。
Client:使用原型的客户端,首先要获取到原型实例对象,然后通过原型实例克隆自身来创建新的实例对象。
以上是原型模式的通用模型,其中具体实现类可以随意添加多个。由此,我们可以得到上述例子,使用原型模型以后的结构。
其结构图,如上述原型模型一致,定义一个接口类,即接口对应的多个实现类,再给定一个订单处理类,如下图所示:
复制谁?当然就是复制这个对象的实例,复制实例就是连带它的数据一起复制,并在堆区开辟相应的空间,而不是复制当前实例,直接returne一个this。
谁来复制?应当让对应的实例对象去复制,因为这个clone方法就是这个实例的成员方法,而且自己复制自己。
由于现在存在订单的接口,因此就把这个要求克隆自身的方法定义在订单的接口里面。代码如下所示:
/**
* 订单的接口,声明了可以克隆自身的方法
*/
public interface OrderApi {
public int getOrderProductNum();
public void setOrderProductNum(int num);
/**
* 克隆方法
* @return 订单原型的实例
*/
public OrderApi cloneOrder();
}
public Object clone() {
return this;
}
这个是一个典型错误,这个根本就没有进行复制,只是间接地调用了自己,其在堆区的内存空间是相同的,并没有进行复制,客户端获取的其实都是同一个实例,都是指向同一个内存空间,如果对“克隆体”的数据进行修改,那么也就是修改本体的数据了。这样会造成不可预测的结果。
先来看看个人订单类怎么写吧!代码如下所示:
/**
* 个人订单类
* @author IT191015
*
*/
public class PersonalOrder implements OrderApi{
private String customerName;
private String productId;
private int orderProductNum = 0;
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 int getOrderProductNum() {
return orderProductNum;
}
public void setOrderProductNum(int orderProductNum) {
this.orderProductNum = orderProductNum;
}
@Override
public OrderApi cloneOrder() {
// 创建一个新的订单,然后把本实例的数据复制过去
PersonalOrder order = new PersonalOrder();
order.setCustomerName(this.customerName);
order.setOrderProductNum(orderProductNum);
order.setProductId(this.productId);
return order;
}
@Override
public String toString() {
return "PersonalOrder [customerName=" + customerName + ", productId=" + productId + ", orderProductNum="
+ orderProductNum + "]";
}
}
企业订单类,代码如下所示:
/**
* 企业订单类
*/
public class EnterpriseOrder implements OrderApi {
private String enterpriseName;
private String productId;
private int orderProductNum;
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 int getOrderProductNum() {
return orderProductNum;
}
public void setOrderProductNum(int orderProductNum) {
this.orderProductNum = orderProductNum;
}
@Override
public OrderApi cloneOrder() {
// 创建一个新的订单,然后把本实例的数据复制过去
EnterpriseOrder eo = new EnterpriseOrder();
eo.setEnterpriseName(this.enterpriseName);
eo.setOrderProductNum(orderProductNum);
eo.setProductId(productId);
return eo;
}
}
这里使用订单接口的克隆方法的是订单的处理对象,也就是说,订单的处理对象就是相当于模型结构中的Client。
当然,客户端在调用clone方法之前,还需要先获取得相应的实例对象,有了实例对象,才能调用实例对象的clone,因为clone方法属于该类中的成员方法。
/**
* 订单处理类
*/
public class Orderbusiness {
public void saveOrder(OrderApi order) {
//1.判断当前的预判产品数量是否大于1000
while(order.getOrderProductNum() > 1000) {
//2.如果大于,还继续拆分
//2.1再新建一份订单,跟传入的订单除了数量不一样以外,其他都是一样的
OrderApi newOrderApi = order.cloneOrder();
newOrderApi.setOrderProductNum(1000);
order.setOrderProductNum(order.getOrderProductNum() - 1000);
System.out.println("拆分生成订单 == " + newOrderApi);
}
//3.不超过,那就是直接业务功能处理了,省略了,打印输出
System.out.println("订单 == " + order);
}
}
不过也可以再写Client类来进行测试,体验一下原型设计模式的快乐,OrderApi newOrderApi = order.cloneOrder();就这语句就搞定了刚刚那么多的if语句,符合了设计的开闭原则。