Design Patterns 之原型模式

悠悠 2022-12-25 03:51 268阅读 0赞

原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。在生活中复制的例子非常多,这里不一一列举了。
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzk0MTM2NA_size_16_color_FFFFFF_t_70
原型模式的优点:

  • Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

原型模式的克隆分为浅克隆和深克隆。

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

下面来看一个具体的例子:

创建一个 Horse 类对象,每调用一次 run 方法。该对象的 distance 属性都会递增 10 。

浅克隆

使用 Java 的 Object 的 浅克隆 方式:
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzk0MTM2NA_size_16_color_FFFFFF_t_70 1

Person 类必须先实现 Cloneable 接口。

  1. package com.lsu.prototype.homework;
  2. /** * 抽象原型 * * @author wang suo * @version 1.0 * @date 2020/11/30 0030 13:22 */
  3. public interface Prototype {
  4. /** * 克隆自己 * * @return 返回 obj */
  5. public Object cloneSelf();
  6. }
  7. package com.lsu.prototype.homework;
  8. /** * 人 * * @Author wang suo * @Date 2020/11/30 0030 14:22 * @Version 1.0 */
  9. public class Person implements Cloneable, Prototype {
  10. /** * 坐骑 */
  11. private Horse mount;
  12. Person(Horse horse) {
  13. super();
  14. this.mount = horse;
  15. }
  16. @Override
  17. public Object cloneSelf() {
  18. Object obj = null;
  19. try {
  20. obj = clone();
  21. } catch (CloneNotSupportedException e) {
  22. e.printStackTrace();
  23. }
  24. return obj;
  25. }
  26. /** * 人跑路 */
  27. void run() {
  28. // 使用坐骑跑路
  29. mount.run();
  30. // 显示路程
  31. mount.show();
  32. }
  33. }
  34. package com.lsu.prototype.homework;
  35. import java.io.*;
  36. /** * 马 * * @Author wang suo * @Date 2020/11/30 0030 13:13 * @Version 1.0 */
  37. class Horse implements Serializable {
  38. private int distance = 0;
  39. /** * 每次距离+10 */
  40. void run() {
  41. this.distance += 10;
  42. }
  43. /** * 显示路程 */
  44. void show() {
  45. System.out.println("distance = " + this.distance);
  46. }
  47. }
  48. package com.lsu.prototype.homework;
  49. /** * 测试类 * * @Author wang suo * @Date 2020/11/30 0030 13:12 * @Version 1.0 */
  50. public class Test {
  51. public static void main(String[] args) {
  52. Horse horse = new Horse();
  53. // 第一个骑士
  54. Person p1 = new Person(horse);
  55. // 第二个骑士
  56. Person p2 = (Person) p1.cloneSelf();
  57. p1.run();
  58. p2.run();
  59. }
  60. }

如上代码,我们克隆了一匹马 h2 我们想要的结果是这两头马各走了 10 米,但是结果却将这两者相加了,明明是两头马跑的距离却被算成了一头马跑的距离。

  1. distance = 10
  2. distance = 20
  3. Process finished with exit code 0

这就是浅克隆,如果调用 clone 方法的当前对象拥有的成员变量是一个对象,那么 clone 方法仅仅是复制了当前对象所拥有的对象的 引用 ,并没有复制这个对象所拥有的变量,所以还是同一个对象。

深度克隆

修改 Person 继承自 Serializable 接口。

  1. package com.lsu.prototype.homework;
  2. /** * 抽象原型 * * @author wang suo * @version 1.0 * @date 2020/11/30 0030 13:22 */
  3. public interface Prototype {
  4. /** * 克隆自己 * * @return 返回 obj */
  5. Object cloneSelf();
  6. }
  7. package com.lsu.prototype.homework;
  8. import java.io.*;
  9. /** * 人 * * @Author wang suo * @Date 2020/11/30 0030 14:22 * @Version 1.0 */
  10. public class Person implements Serializable, Prototype {
  11. /** * 坐骑 */
  12. private Horse mount;
  13. Person(Horse horse) {
  14. super();
  15. this.mount = horse;
  16. }
  17. @Override
  18. public Object cloneSelf() {
  19. Object obj = null;
  20. try {
  21. //obj = clone();
  22. // 使用深克隆
  23. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  24. ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
  25. objectOutputStream.writeObject(this);
  26. ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
  27. ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
  28. obj = objectInputStream.readObject();
  29. } catch (IOException | ClassNotFoundException e) {
  30. e.printStackTrace();
  31. }
  32. return obj;
  33. }
  34. /** * 人跑路 */
  35. void run() {
  36. // 使用坐骑跑路
  37. mount.run();
  38. // 显示路程
  39. mount.show();
  40. }
  41. }
  42. package com.lsu.prototype.homework;
  43. import java.io.*;
  44. /** * 马 * * @Author wang suo * @Date 2020/11/30 0030 13:13 * @Version 1.0 */
  45. class Horse implements Serializable {
  46. private int distance = 0;
  47. /** * 每次距离+10 */
  48. void run() {
  49. this.distance += 10;
  50. }
  51. /** * 显示路程 */
  52. void show() {
  53. System.out.println("distance = " + this.distance);
  54. }
  55. }
  56. package com.lsu.prototype.homework;
  57. /** * 测试类 * * @Author wang suo * @Date 2020/11/30 0030 13:12 * @Version 1.0 */
  58. public class Test {
  59. public static void main(String[] args) {
  60. Horse horse = new Horse();
  61. // 第一个骑士
  62. Person p1 = new Person(horse);
  63. // 第二个骑士
  64. Person p2 = (Person) p1.cloneSelf();
  65. p1.run();
  66. p2.run();
  67. }
  68. }

测试可以实现深度复制,即包含的对象也不是同一个了。

  1. distance = 10
  2. distance = 10
  3. Process finished with exit code 0

如果我们使用序列化技术将对象保存到本地,然后在想用的时候,再拿出来使用应该如何实现呢?

就像玩游戏的存档一样,玩了几关之后存档,之后再回来继续玩。

修改 Person 的代码如下即可:

  1. package com.lsu.prototype.homework;
  2. import java.io.*;
  3. /** * 人 * * @Author wang suo * @Date 2020/11/30 0030 14:22 * @Version 1.0 */
  4. public class Person implements Serializable, Prototype {
  5. /** * 坐骑 */
  6. private Horse mount;
  7. Person(Horse horse) {
  8. super();
  9. this.mount = horse;
  10. }
  11. public Horse getMount() {
  12. return mount;
  13. }
  14. @Override
  15. public Object cloneSelf() {
  16. Object obj = null;
  17. try {
  18. //obj = clone();
  19. // 使用深克隆
  20. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  21. ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
  22. objectOutputStream.writeObject(this);
  23. ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
  24. ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
  25. obj = objectInputStream.readObject();
  26. } catch (IOException | ClassNotFoundException e) {
  27. e.printStackTrace();
  28. }
  29. return obj;
  30. }
  31. /** * 文件存档 */
  32. void archive(String path, String fileName) {
  33. File file = new File(path, fileName);
  34. boolean b = true;
  35. ObjectOutputStream objectOutputStream = null;
  36. try {
  37. if (!file.exists()) {
  38. b = file.createNewFile();
  39. }
  40. } catch (IOException e) {
  41. e.printStackTrace();
  42. }
  43. if (b && file.exists()) {
  44. // 写入到文件中
  45. try {
  46. objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
  47. objectOutputStream.writeObject(this);
  48. System.out.println("文件序列化成功,本地地址:"+ path + "文件名:" + fileName);
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. } finally {
  52. // 关闭输出流
  53. try {
  54. if (objectOutputStream != null) {
  55. objectOutputStream.close();
  56. }
  57. } catch (IOException e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. }
  62. }
  63. /** * 读取存档 */
  64. Person readArchive(String path, String fileName) {
  65. File file = new File(path, fileName);
  66. ObjectInputStream objectInputStream = null;
  67. Person obj = null;
  68. // 写入
  69. try {
  70. objectInputStream = new ObjectInputStream(new FileInputStream(file));
  71. obj = (Person) objectInputStream.readObject();
  72. System.out.println("文件读取成功");
  73. } catch (IOException | ClassNotFoundException e) {
  74. e.printStackTrace();
  75. } finally {
  76. // 关闭输入流
  77. try {
  78. if (objectInputStream != null) {
  79. objectInputStream.close();
  80. }
  81. } catch (IOException e) {
  82. e.printStackTrace();
  83. }
  84. }
  85. return obj;
  86. }
  87. /** * 人跑路 */
  88. void run() {
  89. // 使用坐骑跑路
  90. mount.run();
  91. // 显示路程
  92. mount.show();
  93. }
  94. }
  95. public class Test {
  96. public static void main(String[] args) {
  97. Horse horse = new Horse();
  98. // 第一个骑士
  99. Person p1 = new Person(horse);
  100. // 第二个骑士
  101. Person p2 = (Person) p1.cloneSelf();
  102. p1.run();
  103. p2.run();
  104. // fileName = "archive.txt"; path = "D:"
  105. /* 保存到本地 */
  106. p2.archive("D:", "archive.txt");
  107. System.out.println("---------------------------");
  108. System.out.println("---p2 存档成功! 现在距离--" + p2.getMount().getDistance() + "米");
  109. System.out.println("---------------------------");
  110. /* 实现现场恢复 */
  111. Person p3 = p2.readArchive("D:", "archive.txt");
  112. System.out.println("---------------------------");
  113. System.out.println("---读取存档成功! 现在距离--" + p3.getMount().getDistance() + "米");
  114. System.out.println("---------------------------");
  115. p3.run();
  116. }
  117. }

这就是原型模式最佳实践。

发表评论

表情:
评论列表 (有 0 条评论,268人围观)

还没有评论,来说两句吧...

相关阅读

    相关 原型模式(Prototype Pattern

    一、模式动机 原型模式(Prototype Pattern)结构较为简单,它是一种特殊的创建型模式,当需要创建大量相同或者相似对象时,可以通过对一个已有对象的复制获取更多

    相关 Prototype pattern(原型模式)

    原型模式是一种创建型模式,来看下定义和使用场景: 定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象(通过克隆方法,不调用构造函数); 使用场景:创建对象消

    相关 Design Patterns 原型模式

    原型(`Prototype`)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这

    相关 Design Patterns 工厂模式

    现实生活中,原始社会自给自足(没有工厂),农耕社会小作坊(简单工厂,民间酒坊),工业革命流水线(工厂方法,自产自销),现代产业链代工厂(抽象工厂,富士康)。 我们的项目代码同

    相关 Design Patterns 命令模式

    一、前言 在许多设计中,经常会出现一个对象直接请求另一个对象调用其方法以达到某种目的的行为,这里的两个类之间就会出现紧耦合。这很不好,所以我们应该将 方法的请求者 和 方