用复制创造无限可能_六千字详解原型模式

73 阅读20分钟

引言

  原型模式是一种创建型设计模式,允许通过复制现有对象来创建新对象,而无需深入了解其具体类结构和初始化过程。这种方式不仅简化了复杂对象的创建工作,还能够有效提升性能,尤其是在频繁创建相似对象的情况下,接下来将分为场景问题、解决方案、模式介绍、相关知识四个模块由浅入深来学习原型模式。如有疏漏与不足,恳请不吝赐教,非常感谢


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


一、场景问题

  请仔细听题:现在有一个订单系统,里面有一个保存订单的业务功能,在这个业务功能中,客户有一个需求:每当订单的预定产品数量超过1000的时候就需要把订单拆分成两份订单来保存。如果拆分成两份订单后,还是超过1000就继续拆分,直到每份订单的预定产品数不超过1000为止。为什么要拆分,是因为业务需要。根据业务,目前订单分为两种,一种是个人订单,一种是公司订单。现在想要实现一个通用的订单处理系统,也就是说不管具体是什么类型的订单,都要能够正常处理。


1.1、不用模式

  来分析一波,要想实现通用的订单处理而不关心具体的订单类型,订单处理的对象应该面向一个订单的接口或是一个通用的订单对象来编程,这里我们选用面向订单的接口来处理。示例代码如下:

/**
  * 订单接口
  */
public interface IOrderApi {

    /**
     * 获取订单产品数
     * @return 订单产品数
     */
    int getOrderProductNum();

    /**
     * 设置订单产品数
     * @param num 订单产品数
     */
    void setOrderProductNum(int num);
}
/**
  * 个人订单
  */
public class PersonOrderImpl implements IOrderApi {

    private String customerName;

    private String productId;

    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 String getProductId() {
        return productId;
    }

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

    @Override
    public String toString() {
        return "个人订单:{" +
                "customerName='" + customerName + '\'' +
                ", productId='" + productId + '\'' +
                ", orderProductNum=" + orderProductNum +
                '}';
    }
}
/**
  * 企业订单
  */
public class EnterpriseOrderImpl implements IOrderApi {

    private String enterpriseName;

    private String productId;

    private int orderProductNum = 0;

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

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

    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 String toString() {
        return "企业订单:{" +
                "enterpriseName='" + enterpriseName + '\'' +
                ", productId='" + productId + '\'' +
                ", orderProductNum=" + orderProductNum +
                '}';
    }
}
/**
 * 没有使用原型模式的解决方案
 */
public class OrderBusiness {

    /**
     * 创建订单
     *
     * @param order
     */
    public void saveOrder(IOrderApi order){

        // 1、判断当前订单数是否大于1000,
        // 预定数量可能会很大因此采用while循环,直到拆分订单的数量不超过1000
        while (order.getOrderProductNum() > 1000){
            // 2、如果大于1000需要拆分
            // 2.1、再新建一份新的订单,跟传入的订单除了数量外,其他的都相同
            // 思考:我们不知道入参IOrderApi订单具体类型,order是个人订单还是企业订单,无法创建订单呀
            // 解决办法:目前我们就两个订单类型,可以用if判断呀,判断订单类型。
            // 在思考:现在只有两种订单类型,如果有10种、100种、1000种呢,也写if一个一个的判断吗
            //        除了这种解决办法还有别的解决方案吗?
            IOrderApi newOrder = null;
            if (order instanceof PersonOrderImpl){
                // 创建相应的订单对象
                PersonOrderImpl p2 = new PersonOrderImpl();
                PersonOrderImpl p1 = (PersonOrderImpl) order;
                p2.setProductId(p1.getProductId());
                p2.setCustomerName(p1.getCustomerName());
                p2.setOrderProductNum(1000);
                newOrder = p2;
            }else if (order instanceof EnterpriseOrderImpl){
                // 创建相应的订单对象
                EnterpriseOrderImpl p2 = new EnterpriseOrderImpl();
                EnterpriseOrderImpl p1 = (EnterpriseOrderImpl) order;
                p2.setProductId(p1.getProductId());
                p2.setEnterpriseName(p1.getEnterpriseName());
                p2.setOrderProductNum(1000);
                newOrder = p2;
            }

            // 2.2、原来的订单保留,原订单数量减少1000
            order.setOrderProductNum(order.getOrderProductNum() - 1000);
            System.out.println("新订单hashCode:" + newOrder.hashCode() + ",订单信息:" + newOrder.toString());
        }
        System.out.println("原订单hashCode:" + order.hashCode() + ",订单信息:" + order.toString());
    }
}
public class Main {

    public static void main(String[] args) {

        PersonOrderImpl personOrder = new PersonOrderImpl();
        personOrder.setProductId("10001");
        personOrder.setOrderProductNum(2588);
        personOrder.setCustomerName("超帅");

        OrderBusiness business = new OrderBusiness();
        business.saveOrder(personOrder);
    }
}

//打印结果如下:
/*
新订单hashCode:356573597,订单信息:个人订单:{customerName='超帅', productId='10001', orderProductNum=1000}
新订单hashCode:1735600054,订单信息:个人订单:{customerName='超帅', productId='10001', orderProductNum=1000}
原订单hashCode:21685669,订单信息:个人订单:{customerName='超帅', productId='10001', orderProductNum=588}
*/

1.2、有何问题

  上面的示例已经把功能实现了,好像也能够通用地进行订单处理,而不关心订单的类型和具体实现这样的功能。

  仔细想想,真的没有关心订单的类型和具体实现吗?

  答案是"否定的"。因为我们在处理订单的过程中,使用的ifinstanceof来判断的订单具体类型。

  那这样做有什么不妥的地方吗?

  • 既然要实现通用的订单处理,在处理的过程中不应该知道订单的具体类型,更不应该依赖于订单类型。
  • 这种方式难以扩展,如果新来一种"帅哥专用订单"类型是不是要修改代码在加一个if else,如果来100个类型订单呢

  上面是一种解决方案,有没有更好的方案呢?把上面的问题再抽象描述一下:已经有了某个对象的实例后,如何能够快速简单地创建出来更多的这种对象呢?


二、解决方案

  运用模式的解决思路:在上面的示例中,saveOrder方法里面,已经有了订单接口类型的对象实例,是从外部传入的,我们不知道IOrderApi order它的具体类型是个人订单还是企业订单,但是现在需要在这个方法里面创建一个这样的订单对象,看起来像是通过接口来创建对象一样。

  原型模式就可以解决这样的问题,原型模式会要求对象实现一个可以克隆自身的接口,这样就可以通过拷贝或者克隆一个实例对象本身来创建一个新的实例,如果把这个方法定义在接口上,看起来就像是通过接口来创建了新的接口对象。

  这样一来,通过原型实例创建新的对象,就不需要关心这个实例本身的类型,也不关心它的具体实现,只需要它实现了克隆自身的方法,就可以通过这个方法来获取新的对象,无需再去通过new来创建。


2.1、结构说明

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

image-20241226225704889


2.2、使用模式

  使用模式来重写示例,先要在订单接口上定义出克隆的接口,子类实现订单接口的克隆方法,然后要求各个具体的订单对象克隆自身,这样就可以解决:在订单处理对象里面通过订单接口来创建新的订单对象的问题。

  • 复制谁:复制这个对象的实例,复制实例的意思是连带着数据一块复制。
  • 谁来复制:这个类的实例自己来复制,自己复制自己
  • 如何克隆:具体订单类实现看克隆方法后,在方法里面new一个自己对象实例,然后自己实例的数据取出来,放到新对象实例中去。

image-20241226231859203

/**
  * 订单接口
  */
public interface IOrderApi {

    /**
     * 获取订单产品数
     * @return 订单产品数
     */
    int getOrderProductNum();

    /**
     * 设置订单产品数
     * @param num 订单产品数
     */
    void setOrderProductNum(int num);

    /**
     * 克隆方法
     * @return
     */
    IOrderApi cloneOrder();
}
/**
  * 个人订单
  */
public class PersonOrderImpl implements IOrderApi {

    private String customerName;

    private String productId;

    private int orderProductNum = 0;
    
    // 省略 getter setter toString
    
    /**
      * 克隆方法
      */
    public IOrderApi cloneOrder() {
        PersonOrderImpl pOrder = new PersonOrderImpl();
        pOrder.setCustomerName(this.customerName);
        pOrder.setOrderProductNum(this.orderProductNum);
        pOrder.setProductId(this.productId);
        return pOrder;
    }
}
/**
  * 企业订单
  */
public class EnterpriseOrderImpl implements IOrderApi {

    private String enterpriseName;

    private String productId;

    private int orderProductNum = 0;
    
    // 省略 getter setter toString
    
   /**
     * 克隆方法
     * @return
     */
    public IOrderApi cloneOrder() {
        EnterpriseOrderImpl pOrder = new EnterpriseOrderImpl();
        pOrder.setEnterpriseName(this.enterpriseName);
        pOrder.setOrderProductNum(this.orderProductNum);
        pOrder.setProductId(this.productId);
        return pOrder;
    }
}
/**
 * 使用原型模式的解决方案
 */
public class OrderBusinessUsePattern {

    /** 创建订单
     * @param order
     */
    public void saveOrder(IOrderApi order){

        // 1、判断当前订单数是否大于1000
        while (order.getOrderProductNum() > 1000){
            // 2、如果大于1000需要拆分
            // 2.1、再新建一份新的订单,跟传入的订单除了数量外,其他的都相同
            IOrderApi newOrder = order.cloneOrder();
            newOrder.setOrderProductNum(1000);

            // 2.2、原订单数量减少1000
            order.setOrderProductNum(order.getOrderProductNum() - 1000);
            System.out.println("新订单hashCode:" + newOrder.hashCode() + ",订单信息:" + order.toString());
        }
        System.out.println("原订单hashCode:" + order.hashCode() + ",订单信息:" + order.toString());
    }
}

/**
 * 原型模式
 */
public class Main {

    public static void main(String[] args) {

        PersonOrderImpl personOrder = new PersonOrderImpl();
        personOrder.setProductId("10001");
        personOrder.setOrderProductNum(2588);
        personOrder.setCustomerName("超帅");

        // 使用模式
        OrderBusinessUsePattern businessUsePattern = new OrderBusinessUsePattern();
        businessUsePattern.saveOrder(personOrder);
    }
}
/*
新订单hashCode:356573597,订单信息:个人订单:{customerName='超帅', productId='10001', orderProductNum=1588}
新订单hashCode:1735600054,订单信息:个人订单:{customerName='超帅', productId='10001', orderProductNum=588}
原订单hashCode:21685669,订单信息:个人订单:{customerName='超帅', productId='10001', orderProductNum=588}
*/

三、模式介绍

3.1、认识原型模式

3.1.1、原型模式的功能

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

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


3.1.2、原型与new

  原型模式从某种意义上说,就是new操作,在前面的例子实现中,克隆方法就是使用new来实现的。但请注意,只是类似于new而不是就是new

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


3.1.3、原型实例和克隆的实例

  原型实例和克隆出来的实例,本质上是不同的实例,克隆完成后,它们之间是没有关联的。如果克隆完成后,克隆出来的实例属性值发生了改变,是不会影响到原型实例的。原型实例和克隆出来的实例是完全独立的,它们指向不同的内存空间。


3.1.4、原型模式调用顺序示意图

image-20241227230936250


3.2、原型的优缺点

优点:

  • 对客户隐藏具体的实现

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

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

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

缺点:

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

3.3、思考原型模式

  原型模式的本质:克隆生成对象。克隆是手段,目的是生成新的对象实例。原型模式可以用来解决只知道接口而不知道实现类的问题,使用原型模式,可以出现一种独特的接口造接口的现象,这在面向接口编程中很有作用。同样的功能也可以考虑工厂来实现。

  原型模式的重心还是在创建新的对象实例,至于创建出来的对象,其属性的值是否一定要和原型对象的值完全一样,没有强制规定,要根据实际需求来定。


3.4、何时选用

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

四、相关知识

4.1、浅度克隆深度克隆

  浅度克隆深度克隆和浅拷贝深拷贝是不同表达所指的是一个意思。

  • 浅度克隆(浅拷贝):只负责克隆按值传递的数据。
  • 深度克隆(深拷贝):除了浅度克隆需要克隆的值以外,还要负责克隆引用类型的数据。

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

  问题来了,为什么需要克隆?当需要新的对象保存已有对象的状态时需要克隆。接下来使用Object中的clone()来演示浅拷贝深拷贝。


4.1.1、浅度克隆示例

// 产品原型接口
public interface ProductPrototype {
    ProductPrototype cloneProduct();
}

// 产品类
public class Product implements ProductPrototype{

    private String prodId;

    private String prodName;
    
    // 省略 getter setter
    
    @Override
    public String toString() {
        return "Product{" +
                "hashCode='" + this.hashCode() + '\'' +
                ",prodId='" + prodId + '\'' +
                ", prodName='" + prodName + '\'' +
                '}';
    }

    /**
     * 克隆方法 
     * @return
     */
    public ProductPrototype cloneProduct() {
        // 创建新的产品,把原来实例的数据复制到新实例中
        Product p = new Product();
        p.setProdId(this.prodId);
        p.setProdName(this.prodName);
        return p;
    }
}
// 用个人订单类来展示clone(),企业订单类同理。
public class PersonOrderImpl implements Cloneable,IOrderApi {

    private String customerName;

    private String productId;

    private int orderProductNum = 0;
    
    // 这个属性是产品类型
    private Product product;

    // 省略 getter setter toString
    
    /**
     * 克隆方法 方式一
     * @return
     */
    public IOrderApi cloneOrder() {
        PersonOrderImpl pOrder = new PersonOrderImpl();
        pOrder.setCustomerName(this.customerName);
        pOrder.setOrderProductNum(this.orderProductNum);
        pOrder.setProductId(this.productId);
        pOrder.setProduct((Product)this.product);
        return pOrder;
    }

    /**
     * 克隆方法  方式二
     *
     * 注意:父类是protected修饰,方法是保护方法,因此不能在类的外部直接调用。
     *      如果希望在自己的类中调用,需要重写该方法并设置为 public。
     *      
     * @return Object
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {

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

    public static void main(String[] args) {

        PersonOrderImpl po1 = new PersonOrderImpl();
        po1.setProductId("10001");
        po1.setOrderProductNum(2588);
        po1.setCustomerName("超帅");
        Product product = new Product();
        product.setProdId("p10001");
        product.setProdName("产品一");
        po1.setProduct(product);

        // 演示重写Object的clone() 浅拷贝
        try {
            PersonOrderImpl po2 = (PersonOrderImpl)po1.clone();
            po2.setCustomerName("痞帅");
            po2.getProduct().setProdName("产品二");
            System.out.println("原订单:" + po1.toString());
            System.out.println("新订单:" + po2.toString());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }


        // 演示重写Object的clone() 浅拷贝
        System.out.println("---------------------");
        try {
            PersonOrderImpl po2 = (PersonOrderImpl)po1.cloneOrder();
            po2.setCustomerName("痞帅");
            po2.getProduct().setProdName("产品二");
            System.out.println("原订单:" + po1.toString());
            System.out.println("新订单:" + po2.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

/* 打印结果如下:
原订单:356573597,订单信息:个人订单{hashCode='356573597',customerName='超帅', productId='10001', orderProductNum=2588, product=Product{hashCode='1735600054',prodId='p10001', prodName='产品二'}}
新订单:21685669,订单信息:个人订单{hashCode='21685669',customerName='痞帅', productId='10001', orderProductNum=2588, product=Product{hashCode='1735600054',prodId='p10001', prodName='产品二'}}
---------------------
原订单:个人订单{hashCode='356573597',customerName='超帅', productId='10001', orderProductNum=2588, product=Product{hashCode='1735600054',prodId='p10001', prodName='产品二'}}
新订单:个人订单{hashCode='2133927002',customerName='痞帅', productId='10001', orderProductNum=2588, product=Product{hashCode='1735600054',prodId='p10001', prodName='产品二'}}
 */

  这里用了两种方式进行的浅度克隆,方式一cloneOrder()是通过new来实现的,方式二clone()是通过重写了Objectclone()方法来实现的,最终的结果是一致的。来看一下原订单和新订单product属性的hashCode都是1735600054,且在进行 po2.getProduct().setProdName("产品二");时影响了原订单的属性值,说明在复制生成新订单po2的过程中并没有新建产品而是直接把原订单po1 product属性的内存地址复制过去了,新订单和原定的product属性指向的是同一块内存地址,是同一个产品。


4.1.2、深度克隆示例

// 产品原型接口
public interface ProductPrototype {
    ProductPrototype cloneProduct();
}
// 产品类
public class Product implements Cloneable,ProductPrototype{

    private String prodId;

    private String prodName;
    
    // 省略 getter setter toString

    /**
     * 克隆方法1
     * @return
     */
    public ProductPrototype cloneProduct() {
        // 创建新的产品,把原来实例的数据复制到新实例中
        Product p = new Product();
        p.setProdId(this.prodId);
        p.setProdName(this.prodName);
        return p;
    }

    /**
     * 克隆方法2
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class PersonOrderImpl implements Cloneable,IOrderApi {

    private String customerName;

    private String productId;

    private int orderProductNum = 0;
    
    // 这个属性是产品类型
    private Product product;

    /**
     * 克隆方法  方式一
     * @return
     */
    public IOrderApi cloneOrder() {
        PersonOrderImpl pOrder = new PersonOrderImpl();
        pOrder.setCustomerName(this.customerName);
        pOrder.setOrderProductNum(this.orderProductNum);
        pOrder.setProductId(this.productId);
        //深度克隆的体现
        pOrder.setProduct((Product)this.product.cloneProduct());
        return pOrder;
    }

    /**
     * 克隆方法 方式二
     *
     * 注意:父类是protected修饰,方法是保护方法,因此不能在类的外部直接调用。
     *      如果希望在自己的类中调用,需要重写该方法并设置为 public。
     *
     * @return Object
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {

        PersonOrderImpl obj = null;
        try {
            obj = (PersonOrderImpl)super.clone();
            //深度克隆的体现
            obj.setProduct((Product)this.product.clone());
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return obj;
    }
}
public class Main {

    public static void main(String[] args) {

        PersonOrderImpl po1 = new PersonOrderImpl();
        po1.setProductId("10001");
        po1.setOrderProductNum(2588);
        po1.setCustomerName("超帅");
        Product product = new Product();
        product.setProdId("p10001");
        product.setProdName("产品一");
        po1.setProduct(product);

        // 深拷贝
        try {
            PersonOrderImpl po2 = (PersonOrderImpl)po1.clone();
            po2.setCustomerName("痞帅");
            po2.getProduct().setProdName("产品三");
            System.out.println("原订单:" + po1.toString());
            System.out.println("新订单:" + po2.toString());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        // 深拷贝
        try {
            System.out.println("---------------");
            PersonOrderImpl po2 = (PersonOrderImpl)po1.cloneOrder();
            po2.setCustomerName("痞帅");
            po2.getProduct().setProdName("产品三");
            System.out.println("原订单:" + po1.toString());
            System.out.println("新订单:" + po2.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
}
/* 打印结果:
原订单:个人订单{hashCode='356573597',customerName='超帅', productId='10001', orderProductNum=2588, product=Product{hashCode='1735600054',prodId='p10001', prodName='产品一'}}
新订单:个人订单{hashCode='21685669',customerName='痞帅', productId='10001', orderProductNum=2588, product=Product{hashCode='2133927002',prodId='p10001', prodName='产品三'}}
---------------
原订单:个人订单{hashCode='356573597',customerName='超帅', productId='10001', orderProductNum=2588, product=Product{hashCode='1735600054',prodId='p10001', prodName='产品一'}}
新订单:个人订单{hashCode='1836019240',customerName='痞帅', productId='10001', orderProductNum=2588, product=Product{hashCode='325040804',prodId='p10001', prodName='产品三'}}
*/

  同样用了两种方式,方式二中obj.setProduct((Product)this.product.clone());不可缺少。因为在调用super.clone()的时候,Java先开辟一块内存空间,把实例对象的值原样拷贝过去,对于基础数据类型这样操作没有问题的,而属性product是引用类型,对于引用类型其实是把地址拷贝过去,也就是说克隆后的对象实例product和原型对象实例的product指向的是同一块内存地址。因此要想正确的执行深度克隆,必须手动地对每一个引用类型的属性进行克隆。


4.2、原型管理器

  如果一个系统中原型的数目不固定,比如系统中的原型可以被动态地创建和销毁,那么就需要在系统中维护一个当前可用的原型的注册表,这个注册表就被称为原型管理器。

  如果把原型当成一个资源的话,原型管理器就相当于一个资源管理器,在系统开始运行的时候初始化,然后运行期间可以动态地添加和销毁资源。在这个角度来看,原型管理器就相当于一个缓存资源的实现,只不过里面缓存和管理的是原型实例。

  有了原型管理器后,一般情况下,除了向原型管理器里而添加原型对象的时候是通过new来创建的对象,其余时候都是通过向原型管理器来请求原型实例,然后通过克隆方法来获取新的对象实例,这就可以实现动态管理,或者动态切换具体的实现对象实例。代码如下:

/**
 * 原型管理器
 */
public class PrototypeManager {

    /**
     * 用来记录原型的编号和原型实体的对应关系
     */
    private static Map<String, IOrderApi> map = new HashMap<String, IOrderApi>();

    /**
     * 私有化构造方法
     */
    private PrototypeManager(){

    }

    /**
     * 向原型管理器里面添加或修改某个原型
     * @param prototypeId 原型编号
     * @param orderPrototype 原型示例
     */
    public synchronized static void setPrototype(String prototypeId,IOrderApi orderPrototype){
        map.put(prototypeId,orderPrototype);
    }

    /**
     * 从原型管理器里面删除某个原型
     * @param prototypeId 原型编号
     */
    public synchronized static void removePrototype(String prototypeId){
        map.remove(prototypeId);
    }

    /**
     *
     * @param prototypeId 原型编号
     * @return 原型编号对应的原型
     * @throws Exception
     */
    public synchronized static IOrderApi getPrototype(String prototypeId) throws Exception {
        IOrderApi prototype = map.get(prototypeId);
        if (prototype == null){
            throw new Exception("要找的原型还没有注册或者已被销毁");
        }
        return prototype;
    }
}
public class Main {
    public static void main(String[] args) {
        //原型管理器
        try {
            // 初始化原型管理器
            IOrderApi p1 = new PersonOrderImpl();
            PrototypeManager.setPrototype("prototype001",p1);

            // 获取原型来创建对象
            IOrderApi p3 = PrototypeManager.getPrototype("prototype001");
            p3.setOrderProductNum(3333);
            System.out.println(p3.toString());

            //动态的切换实现
            IOrderApi p2 = new EnterpriseOrderImpl();
            PrototypeManager.setPrototype("prototype001",p2);

            // 重新获取原型来创建对象
            IOrderApi p4 = PrototypeManager.getPrototype("prototype001");
            p4.setOrderProductNum(4444);
            System.out.println(p4);

            // 注销这个原型
            PrototypeManager.removePrototype("prototype001");

            // 再次获取原型来创建对象
            IOrderApi p5 = PrototypeManager.getPrototype("prototype001");
            p5.setOrderProductNum(5555);

        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

/* 打印结果如下
个人订单{hashCode='356573597',customerName='null', productId='null', orderProductNum=3333}
企业订单:{hashCode='1735600054'enterpriseName='null', productId='null', orderProductNum=4444}
要找的原型还没有注册或者已被销毁
*/

  通过上面的代码可以发现,原型管理器类似一个工具类的实现方式,而且对外的几个方法都是加了同步的,这主要是因为如果在多线程环境下使用这个原型管理器的话,那个map属性很明显就成了大家竞争的资源,所以加上同步。


总结

  原型模式是一种通过“克隆”来创建新对象的设计模式。在实际开发中,它可以让我们通过现有的对象实例(即“原型”)创建更多的相同对象,而不需要再用new来重新构建一个新对象。文中若有不当之处,欢迎批评指正,共同进步!

这里我们通过几个问题总结一下全文:

  • 什么是原型模式
  • 原型模式的结构是什么样的
  • 原型模式的优点和缺点
  • 何时选用原型模式,原型模式的使用场景
  • 浅拷贝和深拷贝的区别
  • 原型管理器是怎么实现的