zoukankan      html  css  js  c++  java
  • 设计模式(三)——原型模式 Craftsman

    一、引子

    1、克隆人的问题

      问题:有一个人叫张三,姓名:张三,年龄:18,身高:178。如何创建和张三属性完全相同的10个人呢?
      代码示例:

     1 public class Main {
     2 
     3     public static void main(String[] args) {
     4         Person p0 = new Person("张三", 18, 178);
     5 
     6         Person p1 = new Person(p0.getName(), p0.getAge(), p0.getHeight());
     7         Person p2 = new Person(p0.getName(), p0.getAge(), p0.getHeight());
     8         Person p3 = new Person(p0.getName(), p0.getAge(), p0.getHeight());
     9         Person p4 = new Person(p0.getName(), p0.getAge(), p0.getHeight());
    10         Person p5 = new Person(p0.getName(), p0.getAge(), p0.getHeight());
    11         Person p6 = new Person(p0.getName(), p0.getAge(), p0.getHeight());
    12         Person p7 = new Person(p0.getName(), p0.getAge(), p0.getHeight());
    13         Person p8 = new Person(p0.getName(), p0.getAge(), p0.getHeight());
    14         Person p9 = new Person(p0.getName(), p0.getAge(), p0.getHeight());
    15         Person p10 = new Person(p0.getName(), p0.getAge(), p0.getHeight());
    16 
    17         System.out.println(JSON.toJSONString(p0));
    18     }
    19 }
    20 
    21 class Person {
    22     private String name;
    23     private int age;
    24     private int height;
    25     // getter & setter
    26     // 有参数、无参构造器
    27 }

      这样创建的问题显而易见:如果Person类有100个属性呢?如果要创建和张三属性完全相同的100个人呢?

    二、原型模式

    1、介绍

      原型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象,无需知道创建的细节。简单来说就是复制一个和已知对象一模一样的对象。
      方式:实现 Cloneable 接口,复写 Object 类的 clone() 方法。该方法可以将一个 Java 对象复制一份,但调用该方法的类必须实现Cloneable接口,这是一个标志接口,标识该类能够复制且具有复制的能力。如果不实现 Cloneable 接口,直接调用 clone() 方法,会抛出 CloneNotSupportedException 异常。
      源码示例:Object.clone()

    1 protected native Object clone() throws CloneNotSupportedException;

      这是一个本地方法,具体实现细节,不需要了解,由操作系统实现。只需要知道它的作用就是复制对象,产生一个新的对象。

    2、解决克隆人的问题

      代码示例:实现Cloneable接口,复写clone()方法。

     1 public class Main {
     2 
     3     public static void main(String[] args) {
     4         Person p0 = new Person("张三", 18, 178);
     5 
     6         final Person p1 = p0.clone();
     7 
     8         System.out.println(JSON.toJSONString(p1));
     9         System.out.println(p0 == p1);
    10     }
    11 }
    12 
    13 class Person implements Cloneable {
    14     private String name;
    15     private int age;
    16     private int height;
    17 
    18     @Override
    19     public Person clone() {
    20         Person person = null;
    21         try {
    22             person = (Person) super.clone();
    23         } catch (CloneNotSupportedException e) {
    24             e.printStackTrace();
    25         }
    26         return person;
    27     }
    28 
    29     // getter & setter
    30     // 有参数、无参构造器
    31 }
    32 
    33 // 结果
    34 {"age":18,"height":178,"name":"张三"}
    35 false

      从结果可以看出,复制了一个和张三一模一样的人。

    三、深拷贝与浅拷贝

    1、介绍

      在 Java 中有两种数据类型:基本类型和引用类型。
      基本类型,也称为值类型,有八大基本数据类型:byte、short、int、long、float、double、char、boolean。注:String并不是基本数据类型。
      引用类型,有数组、类、接口、枚举等。

      浅拷贝:只复制基本数据类型,以及引用类型的引用。
      深拷贝:复制基本数据类型,以及引用类型的引用,和引用对象的实例。

      如图所示:克隆"张三"。
      浅拷贝,只会克隆张三的基本属性,如姓名,年龄,身高等,不会克隆他的狗,他两共用这只狗。
      深拷贝,除了克隆张三,还会克隆他的狗,他两分别独立拥有各自的狗。

    2、浅拷贝

      上述在解决克隆人的问题上,其实就是一种浅拷贝。接下来,假设张三拥有一只狗,叫"旺财"。
      代码示例:

     1 public class Main {
     2 
     3     public static void main(String[] args) {
     4         Dog dog = new Dog("旺财");
     5         Person p0 = new Person("张三", 18, 178, dog);
     6 
     7         final Person p1 = p0.clone();
     8 
     9         // 修改p0 张三的狗的名字为 大黄
    10         p0.getDog().setName("大黄");
    11 
    12         System.out.println(JSON.toJSONString(p1));
    13     }
    14 }
    15 
    16 class Person implements Cloneable {
    17     private String name;
    18     private int age;
    19     private int height;
    20     private Dog dog; // 引用类属性
    21 
    22     @Override
    23     public Person clone() {
    24         Person person = null;
    25         try {
    26             person = (Person) super.clone();
    27         } catch (CloneNotSupportedException e) {
    28             e.printStackTrace();
    29         }
    30         return person;
    31     }
    32     // getter & setter
    33     // 有参数、无参构造器
    34 }
    35 
    36 class Dog {
    37     private String name;
    38     // getter & setter
    39     // 有参数、无参构造器
    40 }
    41 
    42 // 结果
    43 {"age":18,"dog":{"name":"大黄"},"height":178,"name":"张三"}

      问题:从结果可以看出,修改了张三的狗的名字,克隆张三的狗的名字也改变了。原因是他两指向的是同一只狗。也就是说对象经过克隆后,只是复制了其引用,其指向的还是同一块堆内存空间,当修改其中一个对象的属性,另一个也会跟着变化。

    3、深拷贝

      如果就想要,在克隆张三后,他两各自拥有自己独立的狗,怎么办呢?就要用到深拷贝。
      实现方式一:让引用类属性也具备可拷贝性。
      代码示例:让Dog同样具备可拷贝性

     1 public class Main {
     2 
     3     public static void main(String[] args) {
     4         Dog dog = new Dog("旺财");
     5         Person p0 = new Person("张三", 18, 178, dog);
     6 
     7         final Person p1 = p0.clone();
     8 
     9         // 修改p0 张三的狗的名字为 大黄
    10         p0.getDog().setName("大黄");
    11 
    12         System.out.println(JSON.toJSONString(p1));
    13         System.out.println(JSON.toJSONString(p0));
    14     }
    15 }
    16 
    17 class Person implements Cloneable {
    18     private String name;
    19     private int age;
    20     private int height;
    21     private Dog dog; // 引用类属性
    22 
    23     @Override
    24     public Person clone() {
    25         Person person = null;
    26         try {
    27             // 1.先克隆一个人
    28             person = (Person) super.clone();
    29 
    30             // 2.再克隆一条狗,并让这个狗属于这个人.
    31             person.dog = this.dog.clone();
    32         } catch (CloneNotSupportedException e) {
    33             e.printStackTrace();
    34         }
    35         return person;
    36     }
    37     // getter & setter
    38     // 有参数、无参构造器
    39 }
    40 
    41 class Dog implements Cloneable {
    42     private String name;
    43 
    44     @Override
    45     public Dog clone() {
    46         Dog dog = null;
    47         try {
    48             dog = (Dog) super.clone();
    49         } catch (CloneNotSupportedException e) {
    50             e.printStackTrace();
    51         }
    52         return dog;
    53     }
    54     // getter & setter
    55     // 有参数、无参构造器
    56 }
    57 
    58 // 结果
    59 {"age":18,"dog":{"name":"旺财"},"height":178,"name":"张三"}
    60 {"age":18,"dog":{"name":"大黄"},"height":178,"name":"张三"}

      问题:从结果可以看出,他两各自拥有了独立的狗。这种方式,确实实现了对象的深拷贝。但是有一个问题,如果Dog类还有引用类属性,也需要同样让其具备可拷贝性,嵌套太深的话,很不利于代码的扩展性。
      一般推荐使用方式二来实现。
      实现方式二:序列化。对序列化不了解的,可以先看这篇,对象流。
      代码示例:序列化

     1 public class Main {
     2 
     3     public static void main(String[] args) throws Exception {
     4         Dog dog = new Dog("旺财");
     5         Person p0 = new Person("张三", 18, 178, dog);
     6         // 序列化
     7         ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("temp.dat"));
     8         output.writeObject(p0);
     9 
    10         // 反序列化
    11         ObjectInputStream input = new ObjectInputStream(new FileInputStream("temp.dat"));
    12         final Person p1 = (Person) input.readObject();
    13 
    14         // 修改p0 张三的狗的名字为 大黄
    15         p0.getDog().setName("大黄");
    16 
    17         System.out.println(JSON.toJSONString(p1));
    18         System.out.println(JSON.toJSONString(p0));
    19     }
    20 }
    21 
    22 class Person implements Serializable {
    23     private static final long serialVersionUID = 1L;
    24     private String name;
    25     private int age;
    26     private int height;
    27     private Dog dog; // 引用类属性
    28     // getter & setter
    29     // 有参数、无参构造器
    30 }
    31 
    32 class Dog implements Serializable {
    33     private static final long serialVersionUID = 1L;
    34     private String name;
    35     // getter & setter
    36     // 有参数、无参构造器
    37 }

      注意:因为使用了序列化,引用类属性,要想序列化,也需要具备可序列化。
      代码示例:优化,可抽取为一个工具类

     1 public class CloneUtils {
     2     @SuppressWarnings("unchecked")
     3     public static <T extends Serializable> T deepClone(T obj) {
     4         T cloneObj = null;
     5         try {
     6             // 写入字节流
     7             ByteArrayOutputStream out = new ByteArrayOutputStream();
     8             ObjectOutputStream obs = new ObjectOutputStream(out);
     9 
    10             obs.writeObject(obj);
    11             obs.close();
    12 
    13             // 分配内存,写入原始对象,生成新对象
    14             ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
    15             ObjectInputStream ois = new ObjectInputStream(ios);
    16 
    17             cloneObj = (T) ois.readObject();
    18             ois.close();
    19         } catch (Exception e) {
    20             e.printStackTrace();
    21         }
    22         return cloneObj;
    23     }
    24 }

    4、优点

      性能高:原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以很好地体现其优点。
      避免构造函数的约束:这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,优点就是减少了约束, 缺点也是减少了约束,需要在实际应用时考虑使用。

    四、原型模型在框架中的应用

    作者:Craftsman-L

    本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。

    如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!

  • 相关阅读:
    Oracle-通过创建索引加快SQL执行效率
    Oracle-DG,MRP进程无法正常应用问题处理,重启大法好
    Oracle-DG,12c pdb创建测试
    Oracle-DG,疑问主库添加日志后,备库未操作主库日志比备库日志数量多,有什么影响?
    Oracle-DG疑问,什么情况下主库会发出一个会话连接备库
    Oracle-DG 主库将log_archive_dest_state_2远程归档线程参数设置为defer,为什么dg还是处于实时同步状态?
    Oracle-rm误删除数据文件,如何强制删除文件启动db
    Oracle-buffer cache过小导致SQL执行时间长
    win10下完全卸载-重装MySQL
    VSCode配置详细教程
  • 原文地址:https://www.cnblogs.com/originator/p/15763457.html
Copyright © 2011-2022 走看看