兄弟,你确定不学会使用“原型设计模式”来创建类吗?

先赞再看,多好的习惯。

兄弟,你确定不学会使用“原型设计模式”来创建类吗?_第1张图片

今天跟大家分享一下设计模式中的“原型模式”。

概述

当系统中需要大量创建相同或者相似的对象时,就可以通过“原型设计模式”来实现。原型模式是“创建型设计模式”中的一种。

原型模式的核心思想是,通过拷贝指定的“原型实例(对象)”,创建跟该对象一样的新对象。简单理解就是“克隆指定对象”。

这里提到的“原型实例(对象)”,就是被克隆的对象,它的作用就是指定要创建的对象种类

兄弟,你确定不学会使用“原型设计模式”来创建类吗?_第2张图片

需要拷贝的原型类必须实现"java.lang.Cloneable"接口,然后重写Object类中的clone方法,从而才可以实现类的拷贝。

Cloneable是一个“标记接口”,所谓的标记接口就是该接口中没有任何内容。标记接口的作用就是为了给所有实现了该接口的类赋予一种特殊的标识。

兄弟,你确定不学会使用“原型设计模式”来创建类吗?_第3张图片

只有当一个类实现了Cloneable接口后,该类才会被赋予调用重写自Object类的clone方法的权利。否则会抛出“CloneNotSupportedException”异常。

兄弟,你确定不学会使用“原型设计模式”来创建类吗?_第4张图片

浅拷贝和深拷贝

原型模式中的拷贝对象可以分为:“浅拷贝”和“深拷贝”。

浅拷贝:

1、当类的成员变量是基本数据类型时,浅拷贝会复制该属性的值赋值给新对象。
2、当成员变量是引用数据类型时,浅拷贝复制的是引用数据类型的地址值。这种情况下,当拷贝出的某一个类修改了引用数据类型的成员变量后,会导致所有拷贝出的类都发生改变。

深拷贝:

深拷贝不仅会复制成员变量为基本数据类型的值,给新对象。还会给是引用数据类型的成员变量申请储存空间,并复制引用数据类型成员变量的对象。这样拷贝出的新对象就不怕修改了是引用数据类型的成员变量后,对其它拷贝出的对象造成影响了。

代码

通过简单的文字描述可能大家对该模式的认知还是不够全面,下面让我们通过代码来深入了解一下。

浅拷贝

案例:一个人,名叫“小菜鸟”,职业是“程序员”。现在因项目需求剧增,导致一个人无法按时完成任务,从而需要克隆出来属性与其一模一样的优秀程序员。

原型类代码:

/**
 * 代表人的类(实现Cloneable接口)
 */
public class Person implements Cloneable {

  // 姓名
  private String name;

  // 职业
  private String occupation;

  // 有参构造方法,方便测试
  ...

  // get()和set()方法
  ...

  // 重写toString(),方便测试时打印类的属性。
  ...

  // 重写Object类的clone方法。
  @Override
  protected Object clone() {
    Person person = null;
    try {
      // 调用父类的方法,完成浅拷贝。
      person = (Person) super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return person;
  }
}

测试类:

public class Test {

  public static void main(String[] args) {
    Person prototypePerson = new Person("小菜鸟", "程序员");
    Person clonePerson = (Person) prototypePerson.clone();
    System.out.println("prototypePerson:" + prototypePerson);
    System.out.println("clonePerson:" + clonePerson);
  }
}

执行结果:

兄弟,你确定不学会使用“原型设计模式”来创建类吗?_第5张图片

深拷贝

在Person类中添加一个是引用数据类型的成员属性Computer。

Computer类:

public class Computer implements Serializable, Cloneable{

  private static final long serialVersionUID = 1L;

  // 颜色
  private String colour;

  // 品牌
  private String brand;
  
  // 有参构造方法,方便测试
  ...

  // get()和set()方法
  ...

  // 重写toString(),方便测试时打印类的属性。
  ...

  //因为该类的成员属性,都是String类型的,所以直接浅拷贝就可以了。
  @Override
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

原型类:

该类中列举了两种方式,重写clone方法时任选其一即可,推荐使用方式二。

public class Person implements Serializable, Cloneable {

  private static final long serialVersionUID = 1L;

  // 姓名
  private String name;

  // 职业
  private String occupation;

  // 电脑
  private Computer computer;
  
  // 有参构造方法,方便测试
  ...

  // get()和set()方法
  ...

  // 重写toString(),方便测试时打印类的属性。
  ...

  /**
   * 方式一:重写clone方法,实现深拷贝。
   *
   * 注:该方式的缺陷是需要单独处理所有要克隆的类中的引用数据类型。
   */
  @Override
  protected Object clone() {
    Person person = null;
    try {
      // 克隆基本数据类型和String类型的属性
      person = (Person) super.clone();
      // 调用该引用数据类型中的clone方法克隆出来一个新的对象,并赋值给新克隆出的类。
      // 这样操作后,该成员属性的值就不在是复制的地址值,从而解决了浅拷贝的问题。
      person.computer = (Computer) this.computer.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return person;
  }

  /**
   * 方式二:通过序列化对象实现深拷贝。(推荐使用该方式)
   *
   * 注:因为该类中已经有一个重写父类的clone方法了,所以该方式就无法在进行重写了。
   *    实际开发中使用这两中方式中的一种即可。
   */
  public Person deepClone() {
    // 声明流对象
    ByteArrayOutputStream bos = null;
    ByteArrayInputStream bis = null;
    ObjectOutputStream oos = null;
    ObjectInputStream ois = null;

    try {
      // 创建序列化流
      bos = new ByteArrayOutputStream();
      oos = new ObjectOutputStream(bos);
      // 将当前对象以对象流的方式输出
      oos.writeObject(this);

      // 创建反序列化流
      bis = new ByteArrayInputStream(bos.toByteArray());
      ois = new ObjectInputStream(bis);
      // 将流对象反序列化,从而实现类的深拷贝。
      return (Person) ois.readObject();
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    } finally {
      try {
        // 释放资源
        bos.close();
        bis.close();
        oos.close();
        ois.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}

注意:以上两个类中实现Serializable接口是为了完成方式二中通过序列化来完成深拷贝,如果选择方式一可以不实现Serializable接口。

测试类:

public class Test {

  public static void main(String[] args) {
    Person prototypePerson = new Person("小菜鸟", "程序员", new Computer("黑色", "小菜鸟牌笔记本"));
    System.out.println("原型类: " + prototypePerson);
    System.out.println("通过重写clone方法实现深拷贝!");
    Person clonePerson1 = (Person) prototypePerson.clone();
    System.out.println("clonePerson1: " + clonePerson1);

    System.out.println("通过序列化对象的方法实现深拷贝!");
    Person clonePerson2 = prototypePerson.deepClone();
    System.out.println("clonePerson2: " + clonePerson2);
  }
}

执行结果:

兄弟,你确定不学会使用“原型设计模式”来创建类吗?_第6张图片

通过以上两种方式都可以实现类的深拷贝,并且我也已经测试过了,具体测试代码就不在此赘述了,有兴趣的读者可以自己试一下,就修改一下是引用数据类型的成员属性值和clone方法即可。

总结

1、通过原型模式可以简化创建重量级对象的过程,并提高程序的效率。
2、原型设计模式是动态获取对象运行时的状态进行创建对象的。
3、使用原型设计模式可以使代码变的更加灵活,因为当原型类发生变化(增、减属性)时,克隆的对象也会做出相应的改变。
4、对已经创建好的类进行改造,使其支持克隆时需要修改源代码,这就是违背了ocp原则。

今天的分享就到这里了,如果感觉“菜鸟”写的文章还不错,记得点赞加关注呦!你们的支持就是我坚持下去的动力。文章哪里写的有问题的也希望大家可以指出,我会虚心受教。

兄弟,你确定不学会使用“原型设计模式”来创建类吗?_第7张图片

你可能感兴趣的:(java设计模式,设计模式,java,编程语言,经验分享,后端)