研磨设计模式 之 原型模式(Prototype)2 ——跟着cc学设计系列
?
(2)应用原型模式来解决的思路
仔细分析上面的问题,在saveOrder方法里面,已经有了订单接口类型的对象实例,是从外部传入的,但是这里只是知道这个实例对象的种类是订单的接口类型,并不知道其具体的实现类型,也就是不知道它到底是个人订单还是企业订单,但是现在需要在这个方法里面创建一个这样的订单对象,看起来就像是要通过接口来创建对象一样。
原型模式就可以解决这样的问题,原型模式会要求对象实现一个可以“克隆”自身的接口,这样就可以通过拷贝或者是克隆一个实例对象本身,来创建一个新的实例。如果把这个方法定义在接口上,看起来就像是通过接口来创建了新的接口对象。
这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,也不关心它的具体实现,只要它实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。
原型模式的结构如图9.1所示:
?
图9.1? 原型模式结构示意图
Prototype:
声明一个克隆自身的接口,用来约束想要克隆自己的类,要求它们都要实现这里定义的克隆方法。
ConcretePrototype:
实现Prototype接口的类,这些类真正实现了克隆自身的功能。
Client:
使用原型的客户端,首先要获取到原型实例对象,然后通过原型实例克隆自身来创建新的对象实例。
(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所示:
?
图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();”,就用一个订单的原型实例来指定了对象的种类,然后通过克隆这个原型实例来创建出了一个新的对象实例。
看到这里,可能有些朋友会认为:Java的Object里面本身就有clone方法,还用搞得这么麻烦吗?
虽然Java里面有clone方法,上面这么做还是很有意义的,可以让我们更好的、更完整的体会原型这个设计模式。当然,后面会讲述如何使用Java里面的clone方法来实现克隆,不要着急。
?
?
?