定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
类型:对象创建型模式
类图:
- 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方法会比较麻烦,必须要能够递归地让所有的相关对象都有正确的实现克隆。
原型模式的本质
原型模式的本质:克隆生成对象。
克隆是手段,目的是生成新的对象实例。正是因为原型的目的是为了生成新的对象实例,原型模式通常是被归类为创建型模式。
原型模式也可以用来解决“只知道接口而不知具体实现的问题”,使用原型模式,可以出现一种独特的“接口造接口”的景象,这在面向接口编程中很有用。同样的功能也可以考虑使用工厂来实现。
何时选用原型模式
建议在以下情况时选用原型模式。
- 如果一个系统想要独立于它想要使用的对象时,可以使用原型模式,让系统只面向接口编程,在系统需要新的对象的时候,可以通过克隆原型来得到。
- 如果需要实例化的类是在运行时刻动态知道时,可以使用原型模式,通过克隆原型来得到需要的实例。