设计模式详解(四)——原型模式

一、场景问题

在电商系统中,订单服务通常是业务核心模块之一。在提交订单的过程中,往往会出现某个订单数额较大的订单,例如一些企业订单订购公司员工节假日礼品,往往会订购成千上万件,如果不做拆分,就会导致订单票据较长。此时就会有拆分订单的操作,假设最大商品数为500,订单类型为个人订单和企业订单,请设计拆分订单的程序方案(此案例来源于《研磨设计模式》一书)。

二、解决方案

1、常规解决思路

1.1、代码展示

  • 订单抽象层(这里定义为抽象类)

    public abstract class AbstractOrder {
    
        private Integer goodsNum;
    
        //获取订单商品数
        public Integer getGoodsNum() {
            return goodsNum;
        }
    
        //设置订单商品数
        protected void setGoodsNum(Integer goodsNum) {
            this.goodsNum = goodsNum;
        }
    }
    

    虽然没有任何抽象成分,但是还是定义为抽象类,方便扩展

  • 订单具体类

    • 个人订单

      public class PersonalOrder extends AbstractOrder {
          /**
           * 会员ID
           */
          private String memberId;
          /**
           * 会员姓名
           */
          private String memberName;
      
          public String getMemberId() {
              return memberId;
          }
      
          public void setMemberId(String memberId) {
              this.memberId = memberId;
          }
      
          public String getMemberName() {
              return memberName;
          }
      
          public void setMemberName(String memberName) {
              this.memberName = memberName;
          }
      
          @Override
          public String toString() {
              final StringBuilder sb = new StringBuilder("PersonalOrder{");
              sb.append("memberId='").append(memberId).append('\'');
              sb.append(", memberName='").append(memberName).append('\'');
              sb.append(", goodsNum='").append(getGoodsNum()).append('\'');
              sb.append('}');
              return sb.toString();
          }
      }
      
    • 企业订单

      public class CompanyOrder extends AbstractOrder {
          /**
           * 企业编码
           */
          private String companyCode;
          /**
           * 企业名称
           */
          private String companyName;
      
          public String getCompanyCode() {
              return companyCode;
          }
      
          public void setCompanyCode(String companyCode) {
              this.companyCode = companyCode;
          }
      
          public String getCompanyName() {
              return companyName;
          }
      
          public void setCompanyName(String companyName) {
              this.companyName = companyName;
          }
      
          @Override
          public String toString() {
              final StringBuilder sb = new StringBuilder("CompanyOrder{");
              sb.append("companyCode='").append(companyCode).append('\'');
              sb.append(", companyName='").append(companyName).append('\'');
              sb.append(", goodsNum='").append(getGoodsNum()).append('\'');
              sb.append('}');
              return sb.toString();
          }
      }
      
  • 拆单逻辑类

    public class OrderManager {
    
        /**
         * 最大拆分间隔数
         */
        private static final Integer SPLIT_INTERVAL_NUM = 500;
    
        /**
         * 拆分订单
         * @author xianzilei
         **/
        public static List<AbstractOrder> splitOrder(AbstractOrder sourceOrder) {
            List<AbstractOrder> result = new ArrayList<>();
            //个人订单拆分
            if (sourceOrder instanceof PersonalOrder) {
                PersonalOrder personalOrder = (PersonalOrder) sourceOrder;
                //根据间隔进行拆分
                while (personalOrder.getGoodsNum() > SPLIT_INTERVAL_NUM) {
                    //创建新订单
                    PersonalOrder subPersonalOrder = new PersonalOrder();
                    subPersonalOrder.setMemberId(personalOrder.getMemberId());
                    subPersonalOrder.setMemberName(personalOrder.getMemberName());
                    subPersonalOrder.setGoodsNum(SPLIT_INTERVAL_NUM);
                    result.add(subPersonalOrder);
                    personalOrder.setGoodsNum(personalOrder.getGoodsNum() - SPLIT_INTERVAL_NUM);
                }
                result.add(personalOrder);
            }
            //企业订单拆分
            else if (sourceOrder instanceof CompanyOrder) {
                CompanyOrder companyOrder = (CompanyOrder) sourceOrder;
                //根据间隔进行拆分
                while (companyOrder.getGoodsNum() > SPLIT_INTERVAL_NUM) {
                    //创建新订单
                    CompanyOrder subCompanyOrder = new CompanyOrder();
                    subCompanyOrder.setCompanyCode(companyOrder.getCompanyCode());
                    subCompanyOrder.setCompanyName(companyOrder.getCompanyName());
                    subCompanyOrder.setGoodsNum(SPLIT_INTERVAL_NUM);
                    result.add(subCompanyOrder);
                    companyOrder.setGoodsNum(companyOrder.getGoodsNum() - SPLIT_INTERVAL_NUM);
                }
                result.add(companyOrder);
            } else {
                throw new RuntimeException("未知的订单类型!");
            }
            return result;
        }
    }
    

    拆分订单时,根据不同的订单类型进行类型强转,通过new对象set值方式复制原订单基础信息,从而实现拆分逻辑。

1.2、客户端测试

public static void main(String[] args) {
        //创建企业订单
        CompanyOrder companyOrder = new CompanyOrder();
        companyOrder.setCompanyCode("W001");
        companyOrder.setCompanyName("贤子磊的煎饼小店");
        companyOrder.setGoodsNum(1502);

        List<AbstractOrder> companyOrderList = OrderManager.splitOrder(companyOrder);
        for (AbstractOrder order : companyOrderList) {
            System.out.println(order);
        }
    }

输出

CompanyOrder{companyCode='W001', companyName='贤子磊的煎饼小店', goodsNum='500'}
CompanyOrder{companyCode='W001', companyName='贤子磊的煎饼小店', goodsNum='500'}
CompanyOrder{companyCode='W001', companyName='贤子磊的煎饼小店', goodsNum='500'}
CompanyOrder{companyCode='W001', companyName='贤子磊的煎饼小店', goodsNum='2'}

1502的订单拆分成4个订单。

1.3、弊端分析

  • 复制订单的基础信息代码过于繁杂且逻辑简单,如果基础信息过多会导致代码过长。
  • 订单类型如果过多就会导致if和else分支过多,后续新增一种新的订单类型就需要修改代码,违背开闭原则
  • 订单拆分只与订单的商品数量有关,而与具体的订单类型无关,类型强转导致具体的订单类型细节暴露出来

2、原型模式

基于上面的弊端,我们可以通过对象的自我复制,屏蔽订单类型细节

2.1、代码修改

  • 订单抽象层添加订单自我复制的方法

    //复制
    public abstract AbstractOrder selfCopy();
    
  • 每个订单具体类实现自我复制方法

    • 个人订单类

      @Override
      public AbstractOrder selfCopy() {
          PersonalOrder copy = new PersonalOrder();
          copy.setMemberId(this.memberId);
          copy.setMemberName(this.memberName);
          copy.setGoodsNum(getGoodsNum());
          return copy;
      }
      
    • 企业订单类

      @Override
      public AbstractOrder selfCopy() {
          CompanyOrder copy = new CompanyOrder();
          copy.setCompanyCode(this.companyCode);
          copy.setCompanyName(this.companyName);
          copy.setGoodsNum(getGoodsNum());
          return copy;
      }
      
  • 修改拆单逻辑

    public class OrderManager {
    
        /**
         * 最大拆分间隔数
         */
        private static final Integer SPLIT_INTERVAL_NUM = 500;
    
        /**
         * 拆分订单
         * @author xianzilei
         **/
        public static List<AbstractOrder> splitOrder(AbstractOrder sourceOrder) {
            List<AbstractOrder> result = new ArrayList<>();
            //无需判断订单类型,直接拆分
            while (sourceOrder.getGoodsNum() > SPLIT_INTERVAL_NUM) {
                //自我复制出新订单
                AbstractOrder copy = sourceOrder.selfCopy();
                //更新订单数
                copy.setGoodsNum(SPLIT_INTERVAL_NUM);
                result.add(copy);
                sourceOrder.setGoodsNum(sourceOrder.getGoodsNum() - SPLIT_INTERVAL_NUM);
            }
            result.add(sourceOrder);
            return result;
        }
    }
    

2.2、客户端测试

客户端代码同上,输出

CompanyOrder{companyCode='W001', companyName='贤子磊的煎饼小店', goodsNum='500'}
CompanyOrder{companyCode='W001', companyName='贤子磊的煎饼小店', goodsNum='500'}
CompanyOrder{companyCode='W001', companyName='贤子磊的煎饼小店', goodsNum='500'}
CompanyOrder{companyCode='W001', companyName='贤子磊的煎饼小店', goodsNum='2'}

2.3、代码分析

  • 拆分逻辑中无需根据订单的类型进行分别处理,且订单的具体类型被隐藏起来。
  • 订单的复制统一起来,无需走分支。且新增订单类型也无需修改拆分代码,符合开闭原则

三、模式概述

1、模式定义

原型模式(Prototype Pattern):用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。

原型模式规定了要创建的对象的类型,这种方法无需知道复制过程的细节,简化了创建对象的操作,且一定程度上提高系统的性能。

2、模式结构

  • 抽象原型(Abstract Prototype)

    具体原型对象的一些公共规定和约束,可以是接口,也可是抽象类,里面定义抽象克隆方法,要求所有的实现类都要实现。

  • 具体原型类(Concrete Prototype)

    抽象原型类的实现类,需要实现克隆方法,代表是可复制的对象。

  • 访问类(Client)

    一般获取到原型对象,然后通过克隆方法获得新的实例对象。

3、模式结构图

4、优缺点

  • 优点
    • 对客户端隐藏具体的实例类型,从而减少客户端对具体类型的依赖
    • 简化实例的创建细节,便于实现某时刻的对象的副本状态等信息。
  • 缺点
    • 每个对象都必须实现克隆方法,且如果对象发生变化,可能也同时需要修改克隆方法。
    • 当对象存在多层嵌套时,深拷贝时需要考虑每一层对象的复制情况,实现较为复杂(深拷贝下文会说到)。

5、使用场景

  • 对象之间相同或存在大量相似
  • 对象的创建成本过大,例如对象的创建需要繁杂的数据准备或访问权限。

四、模式扩展

1、深拷贝与浅拷贝

在说深拷贝与浅拷贝的概念之前,我们先熟悉一下Java中的基本数据类型和引用数据类型

  • 基本数据类型:Java中共有八大基本数据类型,分别为int、short、long、float、double、byte、boolean、char
  • 引用数据类型:类、接口、数组

其中引用类型保存对象的地址,而基本数据类型就存储自身。而深拷贝和浅拷贝就是在这两种数据类型上进行区分的

1.1、浅拷贝

复制基本数据类型的值、String类型(String类特殊,主要是因为String类的不可变的特性,这里不展开研究,大家可以自行百度)、引用数据类型的内存地址

举个栗子:A对象有int和double属性,另外还有B对象。当对A对象进行浅拷贝时生成新的对象C,int、double为基本数据类型,可以直接复制过来,修改对象C中的这两个不会影响到对象A,但是B对象是引用数据类型,对象C中的只是复制得到的是对象B的引用,如果修改对象B中的某些信息,就会对对象A和C都产生影响,因为二者指向的是同一个对象B。

1.2、深拷贝

对基本数据类型进行值传递,对引用数据类型是创建一个新的对象,并复制其内容,如果内部还有引用数据类型,继续创建新对象,以此类推

还是上面的例子,对A进行深拷贝得到的对象C,这两个对象包含引用数据类型分别指向两个对象,二者互不干扰,只不过二者的值相等而已。

2、Java中的clone()

Java的Object对象中提供了clone()方法,所以的类型都默认继承Object类,所以Java默认给所以对象提高了克隆方法。注意:如果使用Object的clone方法需要实现Cloneable接口,否则会抛出CloneNotSupportedException异常。

protected native Object clone() throws CloneNotSupportedException;

该方法是native方法,底层使用非Java语言来实现的(这里我们就不深究了)。该方法本质还是浅拷贝,所以如果我们想实现深拷贝,一般会重写该方法。

3、如何实现深拷贝

实现深拷贝一般是如下的两种方式

3.1、重写clone()方法

重写目标对象的clone方法,里面我们可以使用new对象等方式创建对象,如果目标对象存在引用类型,对引用类型的对象继续重写clone方法,因此该方式较为麻烦。

  • 代码演示

    //父类中包含基本数据类型、String类型和引用数据类型
    public class Father {
        private int age;
        private String name;
        private Son son;
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Son getSon() {
            return son;
        }
    
        public void setSon(Son son) {
            this.son = son;
        }
    
        @Override
        protected Father clone() throws CloneNotSupportedException {
            Father father = new Father();
            father.setAge(age);
            father.setName(name);
            father.setSon(son.clone());
            return father;
        }
    }
    
    
    //子类只包含基本数据类型和String类型,不包含引用数据类型
    public class Son implements Cloneable{
        private int age;
        private String name;
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        /**
         * 类中只存在基本数据类型和String,可以直接调用父类的clone方法实现深拷贝
         **/
        @Override
        protected Son clone() throws CloneNotSupportedException {
            return (Son) super.clone();
        }
    }
    
  • 客户端测试

    public static void main(String[] args) throws CloneNotSupportedException {
        Father father = new Father();
        father.setAge(50);
        father.setName("父亲");
        Son son = new Son();
        son.setAge(25);
        son.setName("儿子");
        father.setSon(son);
    
        Father copy = father.clone();
        System.out.println(copy == father);
        System.out.println(copy.getSon() == father.getSon());
    }
    

    输出

    false
    false
    

    即完成了深拷贝。

3.2、序列化方式

序列化方式即将对象序列化,再反序列化回来。

  • 代码演示

    //父类,这里需要实现序列化接口
    public class Father implements Serializable{
        private int age;
        private String name;
        private Son son;
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Son getSon() {
            return son;
        }
    
        public void setSon(Son son) {
            this.son = son;
        }
    
        /**
         * 深拷贝
         * @author xianzilei
         **/
        public Father deepClone() throws IOException, ClassNotFoundException {
            //将对象写入流中(序列化)
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(this);
            //从流中取出(反序列化)
            ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            return (Father) objectInputStream.readObject();
        }
    }
    
    //子类,这里需要实现序列化接口
    public class Son implements Serializable {
        private int age;
        private String name;
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
  • 客户端测试

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Father father = new Father();
        father.setAge(50);
        father.setName("父亲");
        Son son = new Son();
        son.setAge(25);
        son.setName("儿子");
        father.setSon(son);
    
        Father copy = father.deepClone();
        System.out.println(copy == father);
        System.out.println(copy.getSon() == father.getSon());
    }
    

    输出:

    false
    false
    

    深拷贝成功!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值