您的位置:首页 > 其它

[01][01][05] 原型模式详解

2020-10-16 21:54 806 查看
  • 3. 分类
  • 5. Cloneable 源码分析
  • 1. 定义

    指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象

    调用者不需要知道任何创建细节,不调用构造函数

    2. 适用场景

    • 类初始化消耗资源较多
    • new 产生的一个对象需要非常繁琐的过程(数据准备,访问权限等)
    • 构造函数比较复杂
    • 循环体重生产大量对象时
    • Spring 中的 scope 就是使用的原型模式

    2.1 痛点问题场景

    你一定遇到过大篇幅 getter,setter 赋值的场景.例如这样的代码

    public void setParam(ExamPaperVo vo){
    ExamPaper examPaper = new ExamPaper();
    //试卷主键
    examPaper.setExaminationPaperId(vo.getExaminationPaperId());
    //剩余时间
    curForm.setLeavTime(examPaper.getLeavTime());
    //单位主键
    curForm.setOrganizationId(examPaper.getOrganizationId());
    //考试主键
    curForm.setId(examPaper.getId());
    //考场主键
    curForm.setExamroomId(examPaper.getExamroomId());
    //用户主键
    curForm.setUserId(examPaper.getUserId());
    //专业
    curForm.setSpecialtyCode(examPaper.getSpecialtyCode());
    //岗位
    curForm.setPostionCode(examPaper.getPostionCode());
    //等级
    curForm.setGradeCode(examPaper.getGradeCode());
    //考试开始时间
    curForm.setExamStartTime(examPaper.getExamStartTime());
    //考试结束时间
    curForm.setExamEndTime(examPaper.getExamEndTime());
    //单选题重要数量
    curForm.setSingleSelectionImpCount(examPaper.getSingleSelectionImpCount());
    //多选题重要数量
    curForm.setMultiSelectionImpCount(examPaper.getMultiSelectionImpCount());
    //判断题重要数量
    curForm.setJudgementImpCount(examPaper.getJudgementImpCount());
    //考试时间
    curForm.setExamTime(examPaper.getExamTime());
    //总分
    curForm.setFullScore(examPaper.getFullScore());
    //及格分
    curForm.setPassScore(examPaper.getPassScore());
    //学员姓名
    curForm.setUserName(examPaper.getUserName());
    //分数
    curForm.setScore(examPaper.getScore());
    //是否及格
    curForm.setResult(examPaper.getResult());
    curForm.setIsPassed(examPaper.getIsPassed());
    //单选答对数量
    curForm.setSingleOkCount(examPaper.getSingleOkCount());
    //多选答对数量
    curForm.setMultiOkCount(examPaper.getMultiOkCount());
    //判断答对数量
    curForm.setJudgementOkCount(examPaper.getJudgementOkCount());
    //提交试卷
    service.submit(examPaper);
    }

    代码非常工整,命名非常规范,注释也写的很全面,大家觉得这样的代码优雅吗?我认为,这样的代码属于纯体力劳动.那么原型模式,能帮助我们解决这样的问题

    3. 分类

    • 浅克隆:指拷贝对象时仅仅 copy 对象本身和对象中的基本变量,而不拷贝对象包含的引用指向的对象
    • 深克隆:不仅 copy 对象本身,而且 copy 对象包含的引用指向的所有对象

    3.1 浅克隆实现

    • 原型 prototype 接口

      public interface Prototype {
      Prototype clone();
      }
    • 需要克隆类

      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class ConcretePrototypeA implements Prototype {
      /**
      * 年龄
      */
      private int age;
      /**
      * 姓名
      */
      private String name;
      /**
      * 兴趣爱好
      */
      private List hobbies;
      @Override
      public Prototype clone() {
      return new ConcretePrototypeA(this.age, this.name, this.hobbies);
      }
      }
    • 测试类

      package com.zhunongyun.toalibaba.designpatterns.prototype.simelp;

    import java.util.ArrayList;

    public class PrototypeTest {

    public static void main(String[] args) {
    ConcretePrototypeA concretePrototypeA = new ConcretePrototypeA(18, "test_name", new ArrayList());
    
    ConcretePrototypeA copy = (ConcretePrototypeA) concretePrototypeA.clone();
    
    System.out.println("原对象与克隆对象内存地址对比, 地址是否相同:"
    + (concretePrototypeA == copy));
    
    System.out.println("原对象与克隆对象中引用类型内存地址对比, 地址是否相同:"
    + (concretePrototypeA.getHobbies() == copy.getHobbies()));
    }

    }

    运行结果
    ![](https://huaweirookie.oss-cn-shenzhen.aliyuncs.com/20200715213356.jpg)
    
    从测试结果看出 hobbies 的引用地址是相同的,意味着复制的不是值,而是引用的地址.这样的话,如果我们修改任意一个对象中的属性值,concretePrototype 和 concretePrototypeCone 的 hobbies 值都会改变.这就是我们常说的浅克隆.只是完整复制了值类型数据,没有赋值引用对象
    
    * 浅克隆对于基本数据类型和 String 类型字段会重新申请内存复制数据,克隆对象会指向新的内存地址
    * 浅克隆对于引用类型的字段不会重新申请内存,而是把字段的内存地址指向之前原对象字段的内存地址
    
    ## 3.2 深克隆
    定义一个孙悟空类,拔一根毫毛吹出千万个猴子,每个猴子都有属于自己的金箍棒
    
    通过字节码实现深克隆
    
    * 待克隆类
    ```java
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class JinGuBang implements Serializable {
    
    private float height = 100;
    
    private float wide = 10;
    
    public void big() {
    this.height *= 2;
    this.wide *= 2;
    }
    
    public void small() {
    this.height /= 2;
    this.wide /= 2;
    }
    }
    @Data
    public class QiTianDaSheng implements Cloneable, Serializable {
    
    private JinGuBang jinGuBang;
    
    private int height;
    
    private int weight;
    
    private Date birthday;
    
    public QiTianDaSheng() {
    this.birthday = new Date();
    this.jinGuBang = new JinGuBang();
    }
    
    @Override
    protected Object clone() {
    return this.deepClone();
    }
    
    public Object deepClone() {
    ByteArrayOutputStream byteArrayOutputStream = null;
    ObjectOutputStream objectOutputStream = null;
    
    ByteArrayInputStream byteArrayInputStream = null;
    ObjectInputStream objectInputStream = null;
    
    // 内存中完成操作,对象读写,是通过字节码直接操作
    // 序列化操作类似
    try {
    byteArrayOutputStream = new ByteArrayOutputStream();
    objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
    objectOutputStream.writeObject(this);
    
    byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
    objectInputStream = new ObjectInputStream(byteArrayInputStream);
    
    // 完整的新的对象, new 出来一个新的对象
    QiTianDaSheng copy = (QiTianDaSheng) objectInputStream.readObject();
    copy.setBirthday(new Date());
    
    return copy;
    } catch (Exception e) {
    e.printStackTrace();
    return null;
    } finally {
    IOUtils.closeQuietly(objectInputStream);
    IOUtils.closeQuietly(byteArrayInputStream);
    IOUtils.closeQuietly(objectOutputStream);
    IOUtils.closeQuietly(byteArrayOutputStream);
    }
    }
    }
    • 测试类
      ```java
      package com.zhunongyun.toalibaba.designpatterns.prototype.deep;

    public class DeepCloneTest {
    public static void main(String[] args) {
    QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();

    QiTianDaSheng clone = (QiTianDaSheng) qiTianDaSheng.clone();
    
    System.out.println("深克隆,对象中的引用类型字段 JinGuBang,内存地址是否相同:"
    + (qiTianDaSheng.getJinGuBang() == clone.getJinGuBang()));
    }

    }

    运行结果
    ![b4d128ce939829eeeff142cf1514531f.png](evernotecid://0B7710CE-D342-4510-A5F9-81F7B62D33F2/wwwevernotecom/149352153/ENResource/p155)
    
    # 4. 克隆破坏单例模式
    如果我们克隆的目标的对象是单例对象,那意味着,深克隆就会破坏单例.实际上防止克隆破坏单例解决思路非常简单,禁止深克隆便可.要么你我们的单例类不实现 Cloneable 接口;要么我们重写 clone()方法,在 clone 方法中返回单例对象即可,具体代码如下
    ```java
    @Override
    protected Object clone() throws CloneNotSupportedException {
    return INSTANCE;
    }

    5. Cloneable 源码分析

    Cloneable 是标记型的接口,它们内部都没有方法和属性,实现 Cloneable 来表示该对象能被克隆,能使用 Object.clone()方法

    如果没有实现 Cloneable 的类对象调用 clone()就会抛出 CloneNotSupportedException

    在 ArrayList 中存在 clone 方法,但属于浅克隆

    public Object clone() {
    try {
    ArrayList<?> v = (ArrayList<?>) super.clone();
    v.elementData = Arrays.copyOf(elementData, size);
    v.modCount = 0;
    return v;
    } catch (CloneNotSupportedException e) {
    // this shouldn't happen, since we are Cloneable
    throw new InternalError(e);
    }
    }
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: