JAVA设计模式 — 原型模式(Prototype)

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

类型:对象创建型模式

类图:


  • Prototype:声明一个克隆自身的接口,用来约束想要克隆自己的类,要求它们都要实现这里定义的克隆方法。
  • ConcretePrototype:实现Prototype接口的类,这些类真正实现了克隆自身的功能。
  • Client:使用原型的客户端,首先要获取到原型实例对象,然后通过原型实例克隆自身来创建新的对象实例。


原型模式实例代码

1、先来看看原型接口的定义。示例代码如下:

/**
 * 声明一个克隆自身的接口
 * @author FX_SKY
 *
 */
public interface Prototype {

	/**
	 * 克隆自身的方法
	 * @return
	 */
	public Prototype clone();
}

2、接下来看看具体的原型实现对象。示例代码如下:

/**
 * 克隆的具体实现对象
 * @author FX_SKY
 *
 */
public class ConcretePrototype1 implements Prototype {

	public Prototype clone(){
		//最简单的克隆
		Prototype prototype = new ConcretePrototype1();
		return prototype.clone();
	}
}

/**
 * 克隆的具体实现对象
 * @author FX_SKY
 *
 */
public class ConcretePrototype2 implements Prototype {

	public Prototype clone(){
		//最简单的克隆
		Prototype prototype = new ConcretePrototype2();
		return prototype.clone();
	}
}

3、再看看使用原型的客户端。示例代码如下:

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、订单的接口,声明了可以克隆自身的方法

package com.fxsky.designpattern.prototype.demo;

/**
 * 订单的接口,声明了可以克隆自身的方法
 * @author FX_SKY
 *
 */
public interface OrderApi {

	public int getOrderProductNum();
	public void setOrderProductNum(int num);
	
	/**
	 * 克隆自身的方法
	 * @return
	 */
	public OrderApi cloneOrder();
}

2、先看看个人订单对象的实现,实例代码如下:

package com.fxsky.designpattern.prototype.demo;

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;
	}

	@Override
	public int getOrderProductNum() {
		return this.orderProductNum;
	}

	@Override
	public void setOrderProductNum(int num) {
		this.orderProductNum = num;
	}

	@Override
	public String toString() {
		return "[本个人订单的订购人是 =" + customerName + ",订购产品是="
				+ productId + ", 订购数量为=" + orderProductNum + "]";
	}

	@Override
	public OrderApi cloneOrder() {
		//创建一个新的订单,然后把本实例的数据复制过去
		PersonalOrder order = new PersonalOrder();
		order.setCustomerName(this.customerName);
		order.setProductId(this.productId);
		order.setOrderProductNum(this.orderProductNum);
		
		return order;
	}

}

3、接下来看看企业订单的具体实现,实例代码如下:

package com.fxsky.designpattern.prototype.demo;

public class EnterpriseOrder implements OrderApi {
	private String enterpriseName;
	private String productId;
	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 "[本企业订单的订购企业是 =" + enterpriseName + ",订购产品是="
				+ productId + ", 订购数量为=" + orderProductNum + "]";
	}

	@Override
	public OrderApi cloneOrder() {
		//创建一个新的订单,然后把本实例的数据复制过去
		EnterpriseOrder order = new EnterpriseOrder();
		order.setEnterpriseName(this.enterpriseName);
		order.setProductId(this.productId);
		order.setOrderProductNum(this.orderProductNum);
		
		return order;
	}

}

3、处理订单的业务对象,实例代码如下:

package com.fxsky.designpattern.prototype.demo;

/**
 * 处理订单的业务对象
 * @author FX_SKY
 *
 */
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、客户端测试代码

package com.fxsky.designpattern.prototype.demo;

public class OrderClient {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		PersonalOrder order = new PersonalOrder();
		order.setCustomerName("张三");
		order.setProductId("P0001");
		order.setOrderProductNum(2596);
		
		OrderBusiness business = new OrderBusiness();
		business.saveOrder(order);
	}

}

原型模式的功能

原型模式的功能实际上包含两个方面:

  • 一个是通过克隆来创建新的对象实例
  • 另一个是为克隆出来的新的对象实例复制原型实例属性的值

原型模式要实现的主要功能就是:通过克隆来创建新的对象实例。一般来讲,新创建出来的实例的数据是和原型实例一样的。但是具体如何实现克隆,需要由程序自行实现,原型模式并没有统一的要求和实现算法。



浅度克隆深度克隆

无论你是自己实现克隆方法,还是采用Java提供的克隆方法,都存在一个浅度克隆和深度克隆的问题,那么什么是浅度克隆?什么是深度克隆呢?简单地解释一下。

  • 浅度克隆:只负责克隆按值传递的数据(比如基本数据类型、String类型)。
  • 深度克隆:除了浅度克隆要克隆的值外,还负责克隆引用类型的数据,基本上就是被被克隆实例所有的属性数据都会被克隆出来。

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


上面的示例中实现的克隆就是典型的浅度克隆。下面来看看如何实现深度克隆。

1、自己实现原型的深度克隆

 为了演示深度克隆,定义一个产品对象,也让它实现克隆的功能,产品对象实现的是一个深度克隆。

(1) 先定义产品的原型接口。示例代码如下:

/**
 * 声明一个克隆产品自身的接口
 * @author FX_SKY
 *
 */
public interface ProductPrototype {

	/**
	 * 克隆产品自身的方法
	 * @return
	 */
	public ProductPrototype cloneProduct();
}

(2) 接下来看看具体的产品对象实现。示例代码如下:

/**
 * 产品对象
 * @author FX_SKY
 *
 */
public class Product implements ProductPrototype {

	private String productId;
	private String productName;
	
	public String getProductId() {
		return productId;
	}

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

	public String getProductName() {
		return productName;
	}

	public void setProductName(String productName) {
		this.productName = productName;
	}

	@Override
	public ProductPrototype cloneProduct() {
		// 创建一个新的产品,然后把本实例的数据复制过去
		Product product = new Product();
		product.setProductId(this.productId);
		product.setProductName(this.productName);
		
		return product;
	}

}

(3) 订单的具体实现。示例代码如下:

/**
 * 个人订单
 * @author FX_SKY
 *
 */
public class PersonalOrder implements OrderApi {
	private String customerName;
	private int orderProductNum = 0;
	private Product product = null;
	
	public Product getProduct() {
		return product;
	}

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

	public String getCustomerName() {
		return customerName;
	}

	public void setCustomerName(String customerName) {
		this.customerName = customerName;
	}

	@Override
	public int getOrderProductNum() {
		return this.orderProductNum;
	}

	@Override
	public void setOrderProductNum(int num) {
		this.orderProductNum = num;
	}

	@Override
	public String toString() {
		return "[本个人订单的订购人是 =" + customerName + ",订购产品是="
				+ this.product.getProductName() + ", 订购数量为=" + orderProductNum + "]";
	}

	@Override
	public OrderApi cloneOrder() {
		//创建一个新的订单,然后把本实例的数据复制过去
		PersonalOrder order = new PersonalOrder();
		order.setCustomerName(this.customerName);
		order.setOrderProductNum(this.orderProductNum);
		
		//对于对象类型的数据,深度克隆的时候需要继续调用这个对象的克隆方法
		order.setProduct((Product) this.product.cloneProduct());
		
		return order;
	}

}

(4) 客户端测试程序。示例代码如下:

public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		PersonalOrder oa1 = new PersonalOrder();
		
		Product product = new Product();
		product.setProductId("P00001");
		product.setProductName("产品1");
		
		oa1.setProduct(product);
		oa1.setOrderProductNum(1200);
		oa1.setCustomerName("李四");
		
		System.out.println("这是第一次获取的对象实例="+oa1);
		
		//通过克隆来获取新的实例
		PersonalOrder oa2 = (PersonalOrder) oa1.cloneOrder();
		
		//修改它的值
		oa2.getProduct().setProductName("产品2");
		oa2.setOrderProductNum(2500);
		
		//输出克隆处理的对象的值
		System.out.println("输出克隆处理的对象的值="+oa2);
		
		//再次输出原型实例的值
		System.out.println("再次输出原型实例的值="+oa1);
	}

}

2、Java中的深度克隆

利用Java中的clone方法来实现深度克隆,大体上和自己做差不多,主要是把实现的接口变成了Cloneable。

(1) 产品类没有太大的不同,主要是把实现的接口变成了Cloneable,这样一来实现克隆的方法就不是cloneProduct方法,而是变成clone方法了;另外一个是clone方法的实现变成使用super.clone(); 了。示例代码如下:

/**
 * 产品对象
 * @author FX_SKY
 *
 */
public class Product implements Cloneable {

	private String productId;
	private String productName;
	
	public String getProductId() {
		return productId;
	}

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

	public String getProductName() {
		return productName;
	}

	public void setProductName(String productName) {
		this.productName = productName;
	}

	@Override
	public Object clone() {

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

}

(2) 具体的订单实现类,除了改变接口外,更重要的是在实现clone方法的时候,不仅调用super.clone();,还必须显示地调用引用类型的属性的clone方法,也就是产品的clone方法。示例代码如下:

/**
 * 个人订单
 * @author FX_SKY
 *
 */
public class PersonalOrder implements Cloneable {
	private String customerName;
	private int orderProductNum = 0;
	private Product product = null;
	
	public Product getProduct() {
		return product;
	}

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

	public String getCustomerName() {
		return customerName;
	}

	public void setCustomerName(String customerName) {
		this.customerName = customerName;
	}

	public int getOrderProductNum() {
		return this.orderProductNum;
	}

	public void setOrderProductNum(int num) {
		this.orderProductNum = num;
	}

	@Override
	public String toString() {
		return "[本个人订单的订购人是 =" + customerName + ",订购产品是="
				+ this.product.getProductName() + ", 订购数量为=" + orderProductNum + "]";
	}

	@Override
	public Object clone() {
		//创建一个新的订单,然后把本实例的数据复制过去
		PersonalOrder obj = null;
		
		try {
			obj = (PersonalOrder) super.clone();
			//下面这一句话不可少
			obj.setProduct((Product) this.product.clone());
			
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return obj;
	}

}

(3) 客户端测试类跟之前的差不多,示例代码如下:

public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		PersonalOrder oa1 = new PersonalOrder();
		
		Product product = new Product();
		product.setProductId("P00001");
		product.setProductName("产品1");
		
		oa1.setProduct(product);
		oa1.setOrderProductNum(1200);
		oa1.setCustomerName("李四");
		
		System.out.println("这是第一次获取的对象实例="+oa1);
		
		//通过克隆来获取新的实例
		PersonalOrder oa2 = (PersonalOrder) oa1.clone();
		
		//修改它的值
		oa2.getProduct().setProductName("产品2");
		oa2.setOrderProductNum(2500);
		
		//输出克隆处理的对象的值
		System.out.println("输出克隆处理的对象的值="+oa2);
		
		//再次输出原型实例的值
		System.out.println("再次输出原型实例的值="+oa1);
	}

}

原型模式的优缺点

原型模式的优点

  • 对客户端隐藏具体的实现类型

原型模式的客户端只知道原型接口的类型,并不知道具体的实现类型,从而减少了客户端对这些具体实现类型的依赖。

  • 在运行的时候动态改变具体的实现类型

原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。

原型模式的缺点

原型模式的最大缺点就在于美国原型的子类都必须实现clone的操作,尤其是包含引用类型的对象是,clone方法会比较麻烦,必须要能够递归地让所有的相关对象都有正确的实现克隆。


原型模式的本质

原型模式的本质:克隆生成对象。

克隆是手段,目的是生成新的对象实例。正是因为原型的目的是为了生成新的对象实例,原型模式通常是被归类为创建型模式。

原型模式也可以用来解决“只知道接口而不知具体实现的问题”,使用原型模式,可以出现一种独特的“接口造接口”的景象,这在面向接口编程中很有用。同样的功能也可以考虑使用工厂来实现。


何时选用原型模式

建议在以下情况时选用原型模式。

  • 如果一个系统想要独立于它想要使用的对象时,可以使用原型模式,让系统只面向接口编程,在系统需要新的对象的时候,可以通过克隆原型来得到。
  • 如果需要实例化的类是在运行时刻动态知道时,可以使用原型模式,通过克隆原型来得到需要的实例。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值