原型模式(Prototype Pattern)

概念

原型模式属于创建型模式,指用实例的原型指定创建对象的种类,并通过克隆这些原型创建新的对象。

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

原型实例和克隆出来的实例,本质上是不同的实例,克隆完成后,它们之前是没有关联的,如果克隆完成后,克隆出来的实例的属性值发生改变,是不会影响到原型实例的。

克隆的形式

浅度克隆

只负责克隆按值传递的数据,比如基本数据类型、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}

总结

原型模式的优缺点

优点
  1. 对客户端隐藏具体的实现类型:原型模式的客户端只知道原型接口的类型,并不知道具体的实现类型,从而减少了客户端对这些具体实现类型的依赖。
  2. 在运行时动态的改变实现类型:原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态的改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个实例了。因为克隆一个原型就类似于实例化一个新类。
缺点
  1. 原型模式最大的缺点在于每个原型的子类都必须实现clone操作,尤其在包含引用类型的对象时,clone方法会比较麻烦,必须要能够递归地让所有的相关对象都要正确的实现克隆。

原型模式的应用场景

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

资料参考

《研磨设计模式》 陈臣 王斌 著

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值