概念
原型模式属于创建型模式,指用实例的原型指定创建对象的种类,并通过克隆这些原型创建新的对象。
通过克隆来创建新的对象实例。一般来讲,新创建出来的实例的数据是和原型实例一样的。但是具体如何实现克隆,需要自行实现,原型模式并没有统一的要求和实现算法。
原型实例和克隆出来的实例,本质上是不同的实例,克隆完成后,它们之前是没有关联的,如果克隆完成后,克隆出来的实例的属性值发生改变,是不会影响到原型实例的。
克隆的形式
浅度克隆
只负责克隆按值传递的数据,比如基本数据类型、String类型等。
深度克隆
除了浅度克隆要克隆的值外,还负责克隆引用类型的数据,基本上就是被克隆实例所的属性数据都会被克隆出来。
解释一下就是:如果被克隆的对象里面的属性数据为引用类型,也就是属性的类型也是对象,则需要一直递归地克隆下去。这也意味着,要想深度克隆成功,必须是整个克隆过程中所涉及的对象都要正确的实现克隆方法,如果其中有一个没有正确的实现克隆,那么就会导致克隆失败。
克隆的方式
手动实现
顾名思义,就是手动实现克隆方法,可以按需去复制自己用到的属性。
public class PersonalOrder {
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;
}
/**
* 手动创建一个克隆方法
*/
public PersonalOrder cloneMe() {
// 创建克隆数据
PersonalOrder newOrder = new PersonalOrder();
// 将原型类中的属性值赋值到克隆类中
newOrder.setCustomerName(this.getCustomerName());
newOrder.setProductId(this.getProductId());
newOrder.setOrderProductNum(this.getOrderProductNum());
// 返回克隆好的对象
return newOrder;
}
}
实现Cloneable接口
在Java语言中已经提供了clone方法,定义在Object类中。需要克隆功能的类,只需要实现java.lang.Cloneable接口。
public class EnterpriseOrder implements Cloneable{
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;
}
public int getOrderProductNum() {
return orderProductNum;
}
public void setOrderProductNum(int orderProductNum) {
this.orderProductNum = orderProductNum;
}
@Override
protected EnterpriseOrder clone() throws CloneNotSupportedException {
// 实现Cloneable后,调用父类的克隆方法即可完成克隆
return (EnterpriseOrder) super.clone();
}
@Override
public String toString() {
return "EnterpriseOrder{" +
"enterpriseName='" + enterpriseName + '\'' +
", productId='" + productId + '\'' +
", orderProductNum=" + orderProductNum +
'}';
}
public static void main(String[] args) throws CloneNotSupportedException {
EnterpriseOrder enterpriseOrder = new EnterpriseOrder();
enterpriseOrder.setOrderProductNum(100);
EnterpriseOrder enterpriseOrder1 = enterpriseOrder.clone();
System.out.println("完成克隆时的订单数量:" + enterpriseOrder1.getOrderProductNum());
enterpriseOrder1.setOrderProductNum(200);
System.out.println("克隆对象修改后的订单数量:" + enterpriseOrder1.getOrderProductNum());
System.out.println("原型对象的订单数量:" + enterpriseOrder.getOrderProductNum());
}
}
原型管理器
如果一个系统中原型的数量不固定,比如系统中的原型可以被动态地创建和销毁,那么就需要在系统中维护一个当前可用的原型注册表,这个注册表就被称为原型管理器。
原型管理器可以理解为一个资源管理器,在系统开始运行时被初始化,然后运行期间可以动态的添加和销毁资源。从这个角度看,原型管理器就相当于一个缓存,只不过缓存的对象是原型实例而已。
原型管理器的简单实现示例:
/**
* 定义一个原型接口:人
*/
public interface IPerson {
public IPerson clone();
public String getName();
public void setName(String name);
}
/**
* 实现原型接口:男人类
*/
public class Man implements IPerson {
private String name;
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
// 实现克隆方法
@Override
public IPerson clone() {
Man tmp = new Man();
tmp.setName(this.name);
return tmp;
}
@Override
public String toString() {
return "Man{" +
"name='" + name + '\'' +
'}';
}
}
/**
* 实现原型接口:女人类
*/
public class Woman implements IPerson {
private String name;
// 实现克隆方法
@Override
public IPerson clone() {
Woman tmp = new Woman();
tmp.setName(this.name);
return tmp;
}
@Override
public String getName() {
return null;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Woman{" +
"name='" + name + '\'' +
'}';
}
}
/**
* 原型管理器
*/
public class PrototypeManager {
/** 用来存储原型ID和原型的对应关系 */
private static Map<String, IPerson> PROTOTYPE_CONTAINER = new HashMap<>();
/**
* 构造方法私有化,避免外部创建实例
*/
private PrototypeManager() {
}
/**
* 向原型管理器中添加或修改原型实例
* 在多线程情况下为了保证原型管理器的线程安全,使用synchronized
* @param prototypeId: 原型ID
* @param person: 原型实例
* @return void
*/
public synchronized static void setProperty(String prototypeId, IPerson person) {
PROTOTYPE_CONTAINER.put(prototypeId, person);
}
/**
* 在原型管理器中删除原型实例
* @param prototypeId: 原型ID
* @return void
*/
public synchronized static void removePrototype(String prototypeId) {
PROTOTYPE_CONTAINER.remove(prototypeId);
}
/**
* 获取原型实例
* @param prototypeId: 原型ID
* @return priv.xuchen.play.manager.IPerson
*/
public synchronized static IPerson getPrototype(String prototypeId) throws Exception {
IPerson person = PROTOTYPE_CONTAINER.get(prototypeId);
if (null == person) {
throw new Exception("原型未注册,原型ID ==> " + prototypeId);
}
return person;
}
public static void main(String[] args) throws Exception {
// 初始化原型管理器
IPerson p1 = new Man();
p1.setName("xiaoming");
PrototypeManager.setProperty("person", p1);
// 从原型管理器中获取一个原型
IPerson p2 = PrototypeManager.getPrototype("person");
System.out.println("p2 ==> " + p2.toString());
// 从获取的原型上创建一个新实例并使用,这里修改他的名子
IPerson p3 = (Man) p2.clone();
p3.setName("xiaoli");
System.out.println("p3 ==> " + p3.toString());
// 切换原型的实现
IPerson p4 = new Woman();
p4.setName("xiaohong");
PrototypeManager.setProperty("person", p4);
// 重新获取原型对象
p2 = PrototypeManager.getPrototype("person");
System.out.println("p2 ==> " + p2.toString());
// 注销这个原型
PrototypeManager.removePrototype("person");
// 重新获取原型对象
p2 = PrototypeManager.getPrototype("person");
}
}
运行结果:
p2 ==> Man{name='xiaoming'}
p3 ==> Man{name='xiaoli'}
p2 ==> Woman{name='xiaohong'}
Exception in thread "main" java.lang.Exception: 原型未注册,原型ID ==> person
at priv.xuchen.play.manager.PrototypeManager.getPrototype(PrototypeManager.java:54)
at priv.xuchen.play.manager.PrototypeManager.main(PrototypeManager.java:88)
应用场景举例
需求:在订单系统中,有一个保存订单的业务功能。在这个功能中,由于客户的生产力原因,要求每当订单的预定产品数量超过1000的时候,就需要把订单拆分成两份订单来保存。如果拆分后的订单其中有一份还是超过1000,就继续拆分,直到每份订单的预定产品数量不超过1000。根据实际业务,目前订单类型被分成两种:个人订单和企业订单,现需要实现一个通用的订单系统,即不论是个人订单还是企业订单都可以自动处理。
/**
* 订单接口
*/
public interface IOrder {
/**
* 获取预定产品数量
* @return int
*/
public int getOrderProductNum();
/**
* 修改预定产品数量
* @param num: 预定产品数量
* @return void
*/
public void setOrderProductNum(int num);
/**
* 克隆一个实例
* @return priv.xuchen.play.demo3.IOrder
*/
public IOrder clone();
}
/**
* 个人订单实例
*/
public class PersonalOrder implements IOrder{
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 orderProductNum;
}
@Override
public void setOrderProductNum(int orderProductNum) {
this.orderProductNum = orderProductNum;
}
@Override
public IOrder clone() {
PersonalOrder newOrder = new PersonalOrder();
newOrder.setCustomerName(this.getCustomerName());
newOrder.setProductId(this.getProductId());
newOrder.setOrderProductNum(this.getOrderProductNum());
return newOrder;
}
@Override
public String toString() {
return "PersonalOrder{" +
"customerName='" + customerName + '\'' +
", productId='" + productId + '\'' +
", orderProductNum=" + orderProductNum +
'}';
}
}
/**
* 企业订单实例
*/
public class EnterpriseOrder implements Cloneable, IOrder{
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 orderProductNum;
}
@Override
public void setOrderProductNum(int orderProductNum) {
this.orderProductNum = orderProductNum;
}
@Override
public IOrder clone() {
EnterpriseOrder newOrder = new EnterpriseOrder();
newOrder.setEnterpriseName(this.getEnterpriseName());
newOrder.setProductId(this.getProductId());
newOrder.setOrderProductNum(this.getOrderProductNum());
return newOrder;
}
@Override
public String toString() {
return "EnterpriseOrder{" +
"enterpriseName='" + enterpriseName + '\'' +
", productId='" + productId + '\'' +
", orderProductNum=" + orderProductNum +
'}';
}
}
/**
* 处理订单业务
*/
public class OrderBusiness {
/**
* 保存订单,如果预定产品数据大于1000,则拆分订单
* @param order: 订单原型
* @return void
*/
public void saveOrder(IOrder order) {
// 判断当前预定产品数量是否大于1000
while (order.getOrderProductNum() > 1000) {
// 大如大于,进行订单拆分
// 通过原型模式,克隆当前订单,修改订单数量,生成了新的符合条件的订单实现
IOrder newOrder = order.clone();
newOrder.setOrderProductNum(1000);
// 原订单保留,调整预定产品数量,减1000
order.setOrderProductNum(order.getOrderProductNum() - 1000);
System.out.println("拆分订单==>" + newOrder);
// 对拆分后的订单进行业务处理或者入库,此处省略。。。
}
// 不超过直接进行业务处理或者入库,此处省略。。。
System.out.println("订单==>" + order);
}
public static void main(String[] args) {
OrderBusiness orderBusiness = new OrderBusiness();
PersonalOrder personalOrder = new PersonalOrder();
personalOrder.setCustomerName("AAA");
personalOrder.setProductId("productIdA");
personalOrder.setOrderProductNum(2999);
orderBusiness.saveOrder(personalOrder);
EnterpriseOrder enterpriseOrder = new EnterpriseOrder();
enterpriseOrder.setEnterpriseName("BBB");
enterpriseOrder.setProductId("productIdB");
enterpriseOrder.setOrderProductNum(3000);
orderBusiness.saveOrder(enterpriseOrder);
}
}
运行结果:
拆分订单==>PersonalOrder{customerName='AAA', productId='productIdA', orderProductNum=1000}
拆分订单==>PersonalOrder{customerName='AAA', productId='productIdA', orderProductNum=1000}
订单==>PersonalOrder{customerName='AAA', productId='productIdA', orderProductNum=999}
拆分订单==>EnterpriseOrder{enterpriseName='BBB', productId='productIdB', orderProductNum=1000}
拆分订单==>EnterpriseOrder{enterpriseName='BBB', productId='productIdB', orderProductNum=1000}
订单==>EnterpriseOrder{enterpriseName='BBB', productId='productIdB', orderProductNum=1000}
总结
原型模式的优缺点
优点
- 对客户端隐藏具体的实现类型:原型模式的客户端只知道原型接口的类型,并不知道具体的实现类型,从而减少了客户端对这些具体实现类型的依赖。
- 在运行时动态的改变实现类型:原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态的改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个实例了。因为克隆一个原型就类似于实例化一个新类。
缺点
- 原型模式最大的缺点在于每个原型的子类都必须实现clone操作,尤其在包含引用类型的对象时,clone方法会比较麻烦,必须要能够递归地让所有的相关对象都要正确的实现克隆。
原型模式的应用场景
- 如果一个系统想要独立于它想要使用的对象时,可以使用原型模式,让系统只面向接口编程,在系统需要新的对象的时候,可以通过克隆原型来得到。
- 如果需要实例化的类是在运行时动态指定的,可以使用原型模式,通过克隆来得到需要的实例
资料参考
《研磨设计模式》 陈臣 王斌 著