大话设计模式九:原型模式(Prototype)

一. 定义

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象


二. 结构和说明


Prototype:声明一个克隆自身的接口,用来约束想要克隆自己的类,要求它们都要实现这里定义的克隆方法。

public interface Prototype {
	/**
	 * 克隆自身的方法
	 * @return 一个从自身克隆出来的对象
	 */
	public Prototype clone();
}
ConcretePrototype:实现Prototype接口的类,这些类真正实现克隆自身的功能
public class ConcretePrototype1 implements Prototype {
	public Prototype clone() {
		//最简单的克隆,新建一个自身对象,由于没有属性,就不去复制值了
		Prototype prototype = new ConcretePrototype1();
		return prototype;
	}
}
Client:使用原型的客户端,首先要获取到原型实例对象,然后通过原型实例克隆自身来创建新的对象实例
public class Client {
	/**
	 * 持有需要使用的原型接口对象
	 */
	private Prototype prototype;
	/**
	 * 构造方法,传入需要使用的原型接口对象
	 * @param prototype 需要使用的原型接口对象
	 */
	public Client(Prototype prototype){
		this.prototype = prototype;
	}
	/**
	 * 示意方法,执行某个功能操作
	 */
	public void operation(){
		//会需要创建原型接口的对象
		Prototype newPrototype = prototype.clone();
	}
}

三. 实际应用:

场景:现在有一个订单处理系统,里面有个保存订单的业务功能,在这个业务功能里面,客户有这么一个需求:每当订单的预定产品数量超过1000的时候,就需要把订单拆成两份订单来保存,如果拆成两份订单后,还是超过1000,则继续拆分,直到每份订单的预定产品数量不超过1000。

根据业务,目前的订单类型被分成两种,一种为个人订单,一种是公司订单;现在想要实现一个通用的订单处理系统,也就是说,不管具体是什么类型的订单,都要能够正常的处理。

不用模式的实现方法:

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;

	// getter and setter
}
3. 企业订单对象
public class EnterpriseOrder implements OrderApi{
	// 企业名称
	private String enterpriseName;
	
	// 产品编号
	private String productId;	
	
	// 订单产品数量
	private int orderProductNum = 0;
	// getter and setter
}
4. 处理订单的业务对象
public class OrderBusiness {
	/**
	 * 创建订单的方法
	 * @param order 订单的接口对象
	 */
	public void saveOrder(OrderApi order) {
		// 根据业务要求,当订单的预定的产品数量超过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:不超过,那就直接业务功能处理,省略了,打印输出,看一下
		System.out.println("订单: " + order);
	}
}
5. client
public class Client {
	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);
	}
}

在业务处理代码中,使用了instanceof进行订单类型的判断,这样实现有何不妥?

1. 如果要实现通用的订单处理类,不应该依赖订单的具体实现。

2. 不好扩展新的订单类型,如果现在要加入一个大客户专用订单类型,那么就需要修改订单处理的对象,加判断。

使用模式的实现方法:

1. 订单接口,添加一个可以克隆自身的方法

public interface OrderApi {
	/**
	 * 获取订单产品数量
	 * @return 订单中产品数量
	 */
	public int getOrderProductNum();
	/**
	 * 设置订单产品数量
	 * @param num 订单产品数量
	 */
	public void setOrderProductNum(int num);
	/**
	 * 克隆方法
	 * @return 订单原型的实例
	 */
	public OrderApi cloneOrder();
}
2. 个人订单实现订单接口
public class PersonalOrder implements OrderApi{
	/**
	 * 订购人员姓名
	 */
	private String customerName;
	/**
	 * 产品编号
	 */
	private String productId;
	/**
	 * 订单产品数量
	 */
	private int orderProductNum = 0;
	
	// getter and setter
	
	public OrderApi cloneOrder() {
		//创建一个新的订单,然后把本实例的数据复制过去
		PersonalOrder order = new PersonalOrder();
		
		order.setCustomerName(this.customerName);
		order.setProductId(this.productId);
		order.setOrderProductNum(this.orderProductNum);
		
		return order;
	}
}
3. 企业订单实现订单接口
public class EnterpriseOrder implements OrderApi{
	/**
	 * 企业名称
	 */
	private String enterpriseName;
	/**
	 * 产品编号
	 */
	private String productId;	
	/**
	 * 订单产品数量
	 */
	private int orderProductNum = 0;
	
	// getter and setter
	
	public OrderApi cloneOrder() {
		//创建一个新的订单,然后把本实例的数据复制过去
		EnterpriseOrder order = new EnterpriseOrder();
		order.setEnterpriseName(this.enterpriseName);
		order.setProductId(this.productId);
		order.setOrderProductNum(this.orderProductNum);
		
		return order;
	}
	
}
4. 处理订单的业务对象,不需要再判断了
public class OrderBusiness {
	/**
	 * 创建订单的方法
	 * 
	 * @param order 订单的接口对象
	 */
	public void saveOrder(OrderApi order) {
		// 根据业务要求,当订单的预定的产品数量超过1000的时候,就需要把订单拆成两份订单
		// 当然如果要做好,这里的1000应该做成常量,这么做是为了演示简单

		// 1:判断当前的预定产品数量是否大于1000
		while (order.getOrderProductNum() > 1000) {
			// 2:如果大于,还需要继续拆分
			// 2.1再新建一份订单,跟传入的订单除了数量不一样外,其他都相同
			OrderApi newOrder = order.cloneOrder();
			// 然后进行赋值,产品数量为1000
			newOrder.setOrderProductNum(1000);

			System.out.println("old order num: " + order.getOrderProductNum() 
					      + " , new Order num: " + newOrder.getOrderProductNum());

			// 2.2原来的订单保留,把数量设置成减少1000
			order.setOrderProductNum(order.getOrderProductNum() - 1000);

			// 然后是业务功能处理,省略了,打印输出,看一下
			System.out.println("拆分生成订单: " + newOrder);
		}
		// 3:不超过,那就直接业务功能处理,省略了,打印输出,看一下
		System.out.println("订单: " + order);

	}
}

四.认识原型模式:

原型模式和new:

原型模式从某种意义上说,就像是new操作,在前面的例子实现中,克隆方法即是使用new来实现的。

克隆方法和new操作最明显的不同在于:new一个对象实例,一般属性是没有值的,或者是只有默认值;如果是克隆得到一个实例,通常属性是有值的,属性的值就是原型对象实例在克隆的时候,原型对象实例的属性的值。


五.浅克隆和深克隆:

在Java语言中已经提供了clone方法,定义在Object类中,需要克隆功能的类只需要实现java.lang.Cloneable接口,这个接口没有需要实现的方法,是一个标识接口。

浅克隆:只负责克隆按值传递的数据(比如:基本数据类型、String类型)

深克隆:除了浅克隆要克隆的值外,还负责克隆引用类型的数据,所有的属性数据都被克隆出来。

对于深克隆,如果被克隆的对象里面的属性数据是引用类型,也即是属性的类型也是对象,那么需要一直递归的克隆下去。这意味着,要想深度克隆成功,必须要整个克隆所涉及的对象都要正确实现克隆方法,如果其中有一个没有正确实现克隆,那么就会导致克隆失败。

1. 个人订单:

public class PersonalOrder implements Cloneable {
	// 订购人员姓名
	private String customerName;
	
	// 产品对象
	private Product product = null;
	
	// 订单产品数量
	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 Product getProduct() {
		return product;
	}

	public void setProduct(Product product) {
		this.product = product;
	}

	public String toString() {
		// 简单点输出
		return "订购产品是=" + this.product.getName() + ",订购数量为=" + this.orderProductNum;
	}
	
	// 深克隆
	public Object clone() {
		PersonalOrder obj = null;
		try {
			obj = (PersonalOrder) super.clone(); // 浅克隆,调用父类clone方法,只能克隆基本属性
			obj.setProduct((Product) this.product.clone()); // 对于引用类型,需要手动调用set方法
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return obj;
	}

}
2. 产品对象
public class Product implements Cloneable {
	// 产品编号
	private String productId;
	
	// 产品名称
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getProductId() {
		return productId;
	}

	public void setProductId(String productId) {
		this.productId = productId;
	}

	public String toString() {
		return "产品编号=" + this.productId + ",产品名称=" + this.name;
	}

	public Object clone() {
		Object obj = null;
		try {
			obj = super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return obj;
	}
}

注意:ProductOrder和Product都实现了Cloneable接口,所以都具有克隆的功能。


你可能感兴趣的:(设计模式,原型模式)