• 设计模式之原型模式


    什么是原型模式

    原型模式即使用已经创建的对象作为原型,通过复制该原型对象得到一个和原型完全相同的新对象。

    具体结构

    原型模式包含以下几种角色:

    1. 抽象原型类,规定了具体原型类必须实现的clone()方法;
    2. 具体原型类,实现了抽象原型类的clone()方法,表示此类可以被复制(克隆);
    3. 访问类,使用具体原型类的clone()方法来创建新的对象。

    实现方法

    原型模式有两种实现方法,一种是浅拷贝,另外一种是深拷贝,也另有说法为浅克隆和深克隆。

    浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原

    有属性所指向的对象的内存地址。

    深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

    由上面的定义可知,浅拷贝对象中,非基本数据类型(对象类型)指向的是原有对象的内存地址,这样会导致修改其中一个对象的非基本数据类型时,另外的一个对象中的相关类型属性也会跟随改变。针对这一点需要特别注意。

    Java中使用Object类中native clone()方法来实现浅拷贝,使用方式是实现Cloneable接口,重写clone()方法,如果不重写方法,由于访问权限的问题,运行会报以下错误。

    java: clone() 在 java.lang.Object 中是 protected 访问控制

    如果不实现Cloneable接口,会报CloneNotSupportedException异常错误。

    所以必须重写clone()方法,修改访问修饰符为public,返回值为具体对象类型。

    浅拷贝

    以下为浅拷贝的实现代码

    1. public class MyPrototype implements Cloneable {//实现Cloneable接口
    2. public MyPrototype() {}
    3. // 需要重写clone方法
    4. @Override
    5. public MyPrototype clone() {
    6. try {
    7. return (MyPrototype) super.clone();
    8. } catch (CloneNotSupportedException e) {
    9. throw new AssertionError();
    10. }
    11. }
    12. }

    深拷贝

    深拷贝有两种实现方式,一是针对类内部的所有非基本数据类型(不包含String类型)的数据,让其实现Cloneable接口,重写其clone()方法。另外一种是使用序列化的方式,把对象先序列化,然后再反序列化回来就可以得到内存隔离的两个对象。这样修改各个对象的数据就能到达互补影响的效果

    以下为示例

    下面定义了Student和Teacher类,作为测试。

    1. //定义Student类
    2. public class Student implements Serializable {
    3. private String studentName;
    4. private int studentId;
    5. public Student(String studentName, int studentId) {
    6. this.studentName = studentName;
    7. this.studentId = studentId;
    8. }
    9. public Student() {
    10. }
    11. public String getStudentName() {
    12. return studentName;
    13. }
    14. public void setStudentName(String studentName) {
    15. this.studentName = studentName;
    16. }
    17. public int getStudentId() {
    18. return studentId;
    19. }
    20. public void setStudentId(int studentId) {
    21. this.studentId = studentId;
    22. }
    23. @Override
    24. public String toString() {
    25. return "Student{" +
    26. "studentName='" + studentName + '\'' +
    27. ", studentId=" + studentId +
    28. '}';
    29. }
    30. }
    31. //定义Teacher类
    32. public class Teacher implements Cloneable, Serializable {
    33. private String teacherName;
    34. private int teacherId;
    35. private List<Student> students;
    36. public Teacher() {
    37. }
    38. public Teacher(String teacherName, int teacherId, List<Student> students) {
    39. this.teacherName = teacherName;
    40. this.teacherId = teacherId;
    41. this.students = students;
    42. }
    43. public String getTeacherName() {
    44. return teacherName;
    45. }
    46. public void setTeacherName(String teacherName) {
    47. this.teacherName = teacherName;
    48. }
    49. public int getTeacherId() {
    50. return teacherId;
    51. }
    52. public void setTeacherId(int teacherId) {
    53. this.teacherId = teacherId;
    54. }
    55. public List<Student> getStudents() {
    56. return students;
    57. }
    58. public void setStudents(List students) {
    59. this.students = students;
    60. }
    61. @Override
    62. public String toString() {
    63. return "Teacher{" +
    64. "teacherName='" + teacherName + '\'' +
    65. ", teacherId=" + teacherId +
    66. ", students=" + students +
    67. '}';
    68. }
    69. @Override
    70. public Teacher clone() {
    71. try {
    72. Teacher clone = (Teacher) super.clone();
    73. return clone;
    74. } catch (CloneNotSupportedException e) {
    75. throw new AssertionError();
    76. }
    77. }
    78. }

    浅拷贝存在的问题,就是修改对象中的非基本数据类型,克隆的对象也会跟随改变,示例如下。

    1. public class PrototypeTest {
    2. public static void main(String[] args) {
    3. Teacher teacher = new Teacher();
    4. teacher.setTeacherName("李明");
    5. teacher.setTeacherId(1001);
    6. List<Student> list = new ArrayList<>();
    7. list.add(new Student("小红", 230001));
    8. list.add(new Student("小亮", 230002));
    9. teacher.setStudents(list);
    10. Teacher newTeacher = teacher.clone();
    11. newTeacher.getStudents().get(0).setStudentName("小冰");
    12. System.out.println(newTeacher);
    13. System.out.println(teacher);
    14. }
    15. }
    16. // 结果如下:
    17. // Teacher{teacherName='李明', teacherId=1001,
    18. //students=[Student{studentName='小冰', studentId=230001},
    19. //Student{studentName='小亮', studentId=230002}]}
    20. // Teacher{teacherName='李明', teacherId=1001,
    21. //students=[Student{studentName='小冰', studentId=230001},
    22. // Student{studentName='小亮', studentId=230002}]}
    23. // 测试结果显示,修改teacher对象后,newTeacher中的数据也一同发生了变化。
    24. // 这是因为Student数据指向了同一块内存区域。
    解决办法一

    一是使用序列化的方式,把对象先序列化,然后再反序列化回来就可以得到内存隔离的两个对象。这样修改各个对象的数据就能到达互补影响的效果。

    1. public class PrototypeTest {
    2. public static void main(String[] args) throws Exception {
    3. Teacher teacher = new Teacher();
    4. teacher.setTeacherName("李明");
    5. teacher.setTeacherId(1001);
    6. List<Student> list = new ArrayList<>();
    7. list.add(new Student("小红", 230001));
    8. list.add(new Student("小亮", 230002));
    9. teacher.setStudents(list);
    10. ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("teacher.ob"));
    11. objectOutputStream.writeObject(teacher);
    12. objectOutputStream.close();
    13. ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("teacher.ob"));
    14. Teacher newTeacher1 = (Teacher) objectInputStream.readObject();
    15. newTeacher1.getStudents().get(0).setStudentName("小梅");
    16. teacher.getStudents().get(0).setStudentName("小美");
    17. System.out.println(teacher);
    18. System.out.println(newTeacher1);
    19. System.out.println(teacher == newTeacher1);
    20. }
    21. }
    22. // 测试结果
    23. // Teacher{teacherName='李明', teacherId=1001,
    24. // students=[Student{studentName='小美', studentId=230001},
    25. // Student{studentName='小亮', studentId=230002}]}
    26. // Teacher{teacherName='李明', teacherId=1001,
    27. // students=[Student{studentName='小梅', studentId=230001},
    28. // Student{studentName='小亮', studentId=230002}]}

    可以看到,使用序列化和反序列化的方式后,得到的对象是内存隔离的,修改各个对象的数据互不影响。

    解决办法二

    针对类内部的所有非基本数据类型(不包含String类型)的数据,让其实现Cloneable接口,重写其clone()方法。

    所以需要让Student 类实现Cloneable接口,重写其clone方法。Teacher类也需要重写其clone方法,针对非基本类型也要调用clone方法,代码如下:

    1. public class Student implements Cloneable {
    2. private String studentName;
    3. private int studentId;
    4. public Student(String studentName, int studentId) {
    5. this.studentName = studentName;
    6. this.studentId = studentId;
    7. }
    8. public Student() {
    9. }
    10. public String getStudentName() {
    11. return studentName;
    12. }
    13. public void setStudentName(String studentName) {
    14. this.studentName = studentName;
    15. }
    16. public int getStudentId() {
    17. return studentId;
    18. }
    19. public void setStudentId(int studentId) {
    20. this.studentId = studentId;
    21. }
    22. @Override
    23. public String toString() {
    24. return "Student{" +
    25. "studentName='" + studentName + '\'' +
    26. ", studentId=" + studentId +
    27. '}';
    28. }
    29. @Override
    30. public Student clone() {
    31. try {
    32. Student clone = (Student) super.clone();
    33. return clone;
    34. } catch (CloneNotSupportedException e) {
    35. throw new AssertionError();
    36. }
    37. }
    38. }

    Teacher类clone方法重写

    1. package com.code.designpattern.prototype;
    2. import java.io.Serializable;
    3. import java.util.ArrayList;
    4. import java.util.List;
    5. public class Teacher implements Cloneable{
    6. private String teacherName;
    7. private int teacherId;
    8. private List<Student> students;
    9. public Teacher() {
    10. }
    11. public Teacher(String teacherName, int teacherId, List<Student> students) {
    12. this.teacherName = teacherName;
    13. this.teacherId = teacherId;
    14. this.students = students;
    15. }
    16. public String getTeacherName() {
    17. return teacherName;
    18. }
    19. public void setTeacherName(String teacherName) {
    20. this.teacherName = teacherName;
    21. }
    22. public int getTeacherId() {
    23. return teacherId;
    24. }
    25. public void setTeacherId(int teacherId) {
    26. this.teacherId = teacherId;
    27. }
    28. public List<Student> getStudents() {
    29. return students;
    30. }
    31. public void setStudents(List students) {
    32. this.students = students;
    33. }
    34. @Override
    35. public String toString() {
    36. return "Teacher{" +
    37. "teacherName='" + teacherName + '\'' +
    38. ", teacherId=" + teacherId +
    39. ", students=" + students +
    40. '}';
    41. }
    42. @Override
    43. public Teacher clone() {
    44. try {
    45. // 重点在于改写这个clone方法,对非基本数据类型都需要调用clone方法重新赋值。
    46. Teacher clone = (Teacher) super.clone();
    47. List<Student> studentsList = clone.getStudents();
    48. List<Student> studentsList2 = new ArrayList<>();
    49. for (Student s:studentsList) {
    50. studentsList2.add(s.clone());
    51. }
    52. clone.setStudents(studentsList2);
    53. return clone;
    54. } catch (CloneNotSupportedException e) {
    55. throw new AssertionError();
    56. }
    57. }
    58. }

    查看最终效果如下:

    1. public class PrototypeTest {
    2. public static void main(String[] args) throws Exception {
    3. Teacher teacher = new Teacher();
    4. teacher.setTeacherName("李明");
    5. teacher.setTeacherId(1001);
    6. List<Student> list = new ArrayList<>();
    7. list.add(new Student("小红", 230001));
    8. list.add(new Student("小亮", 230002));
    9. teacher.setStudents(list);
    10. Teacher newTeacher = teacher.clone();
    11. newTeacher.getStudents().get(0).setStudentName("小冰");
    12. System.out.println(newTeacher);
    13. System.out.println(teacher);
    14. }
    15. }
    16. // 测试输出如下:
    17. //Teacher{teacherName='李明', teacherId=1001,
    18. //students=[Student{studentName='小冰', studentId=230001},
    19. //Student{studentName='小亮', studentId=230002}]}
    20. //Teacher{teacherName='李明', teacherId=1001,
    21. //students=[Student{studentName='小红', studentId=230001},
    22. //Student{studentName='小亮', studentId=230002}]}
    总结:

    一般推荐使用序列化的方法进行深拷贝,实现Cloneable接口重写clone方法的方式有些繁琐。

  • 相关阅读:
    【毕业设计】机器视觉答题卡识别系统 - python 深度学习
    助力工业物联网,工业大数据之服务域:可视化工具Grafana介绍【三十八】
    【Java】认识类和对象
    java 前后端开发 常用下载链接
    ESPHome不经过HA设备1直接控制设备2
    【Unity】Xml的加密读取保存
    2022 年辽宁省大学生程序设计竞赛 个人题解
    Android应用集成RabbitMQ消息处理指南
    .NET C#版本和.NET版本以及VS版本的对应关系
    考虑温度影响的vumat子程序在木材受火后强度分析中的应用
  • 原文地址:https://blog.csdn.net/qq_24054661/article/details/133889633