原型模式即使用已经创建的对象作为原型,通过复制该原型对象得到一个和原型完全相同的新对象。
原型模式包含以下几种角色:

原型模式有两种实现方法,一种是浅拷贝,另外一种是深拷贝,也另有说法为浅克隆和深克隆。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原
有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
由上面的定义可知,浅拷贝对象中,非基本数据类型(对象类型)指向的是原有对象的内存地址,这样会导致修改其中一个对象的非基本数据类型时,另外的一个对象中的相关类型属性也会跟随改变。针对这一点需要特别注意。
Java中使用Object类中native clone()方法来实现浅拷贝,使用方式是实现Cloneable接口,重写clone()方法,如果不重写方法,由于访问权限的问题,运行会报以下错误。
java: clone() 在 java.lang.Object 中是 protected 访问控制
如果不实现Cloneable接口,会报CloneNotSupportedException异常错误。
所以必须重写clone()方法,修改访问修饰符为public,返回值为具体对象类型。
以下为浅拷贝的实现代码
- public class MyPrototype implements Cloneable {//实现Cloneable接口
-
- public MyPrototype() {}
-
- // 需要重写clone方法
- @Override
- public MyPrototype clone() {
- try {
- return (MyPrototype) super.clone();
- } catch (CloneNotSupportedException e) {
- throw new AssertionError();
- }
- }
- }
深拷贝有两种实现方式,一是针对类内部的所有非基本数据类型(不包含String类型)的数据,让其实现Cloneable接口,重写其clone()方法。另外一种是使用序列化的方式,把对象先序列化,然后再反序列化回来就可以得到内存隔离的两个对象。这样修改各个对象的数据就能到达互补影响的效果
以下为示例
下面定义了Student和Teacher类,作为测试。
- //定义Student类
- public class Student implements Serializable {
- private String studentName;
- private int studentId;
-
- public Student(String studentName, int studentId) {
- this.studentName = studentName;
- this.studentId = studentId;
- }
-
- public Student() {
-
- }
-
- public String getStudentName() {
- return studentName;
- }
-
- public void setStudentName(String studentName) {
- this.studentName = studentName;
- }
-
- public int getStudentId() {
- return studentId;
- }
-
- public void setStudentId(int studentId) {
- this.studentId = studentId;
- }
-
- @Override
- public String toString() {
- return "Student{" +
- "studentName='" + studentName + '\'' +
- ", studentId=" + studentId +
- '}';
- }
- }
-
- //定义Teacher类
- public class Teacher implements Cloneable, Serializable {
- private String teacherName;
- private int teacherId;
- private List<Student> students;
-
- public Teacher() {
- }
-
- public Teacher(String teacherName, int teacherId, List<Student> students) {
- this.teacherName = teacherName;
- this.teacherId = teacherId;
- this.students = students;
- }
-
- public String getTeacherName() {
- return teacherName;
- }
-
- public void setTeacherName(String teacherName) {
- this.teacherName = teacherName;
- }
-
- public int getTeacherId() {
- return teacherId;
- }
-
- public void setTeacherId(int teacherId) {
- this.teacherId = teacherId;
- }
-
- public List<Student> getStudents() {
- return students;
- }
-
- public void setStudents(List
students ) { - this.students = students;
- }
-
- @Override
- public String toString() {
- return "Teacher{" +
- "teacherName='" + teacherName + '\'' +
- ", teacherId=" + teacherId +
- ", students=" + students +
- '}';
- }
-
- @Override
- public Teacher clone() {
- try {
- Teacher clone = (Teacher) super.clone();
- return clone;
- } catch (CloneNotSupportedException e) {
- throw new AssertionError();
- }
- }
- }
浅拷贝存在的问题,就是修改对象中的非基本数据类型,克隆的对象也会跟随改变,示例如下。
- public class PrototypeTest {
-
- public static void main(String[] args) {
- Teacher teacher = new Teacher();
- teacher.setTeacherName("李明");
- teacher.setTeacherId(1001);
-
- List<Student> list = new ArrayList<>();
- list.add(new Student("小红", 230001));
- list.add(new Student("小亮", 230002));
- teacher.setStudents(list);
-
- Teacher newTeacher = teacher.clone();
- newTeacher.getStudents().get(0).setStudentName("小冰");
- System.out.println(newTeacher);
- System.out.println(teacher);
- }
- }
- // 结果如下:
- // Teacher{teacherName='李明', teacherId=1001,
- //students=[Student{studentName='小冰', studentId=230001},
- //Student{studentName='小亮', studentId=230002}]}
- // Teacher{teacherName='李明', teacherId=1001,
- //students=[Student{studentName='小冰', studentId=230001},
- // Student{studentName='小亮', studentId=230002}]}
-
-
- // 测试结果显示,修改teacher对象后,newTeacher中的数据也一同发生了变化。
- // 这是因为Student数据指向了同一块内存区域。
一是使用序列化的方式,把对象先序列化,然后再反序列化回来就可以得到内存隔离的两个对象。这样修改各个对象的数据就能到达互补影响的效果。
- public class PrototypeTest {
-
- public static void main(String[] args) throws Exception {
- Teacher teacher = new Teacher();
- teacher.setTeacherName("李明");
- teacher.setTeacherId(1001);
-
- List<Student> list = new ArrayList<>();
- list.add(new Student("小红", 230001));
- list.add(new Student("小亮", 230002));
- teacher.setStudents(list);
-
- ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("teacher.ob"));
- objectOutputStream.writeObject(teacher);
- objectOutputStream.close();
-
- ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("teacher.ob"));
- Teacher newTeacher1 = (Teacher) objectInputStream.readObject();
- newTeacher1.getStudents().get(0).setStudentName("小梅");
- teacher.getStudents().get(0).setStudentName("小美");
- System.out.println(teacher);
- System.out.println(newTeacher1);
- System.out.println(teacher == newTeacher1);
- }
- }
-
- // 测试结果
- // Teacher{teacherName='李明', teacherId=1001,
- // students=[Student{studentName='小美', studentId=230001},
- // Student{studentName='小亮', studentId=230002}]}
- // Teacher{teacherName='李明', teacherId=1001,
- // students=[Student{studentName='小梅', studentId=230001},
- // Student{studentName='小亮', studentId=230002}]}
可以看到,使用序列化和反序列化的方式后,得到的对象是内存隔离的,修改各个对象的数据互不影响。
针对类内部的所有非基本数据类型(不包含String类型)的数据,让其实现Cloneable接口,重写其clone()方法。
所以需要让Student 类实现Cloneable接口,重写其clone方法。Teacher类也需要重写其clone方法,针对非基本类型也要调用clone方法,代码如下:
- public class Student implements Cloneable {
- private String studentName;
- private int studentId;
-
- public Student(String studentName, int studentId) {
- this.studentName = studentName;
- this.studentId = studentId;
- }
-
- public Student() {
-
- }
-
- public String getStudentName() {
- return studentName;
- }
-
- public void setStudentName(String studentName) {
- this.studentName = studentName;
- }
-
- public int getStudentId() {
- return studentId;
- }
-
- public void setStudentId(int studentId) {
- this.studentId = studentId;
- }
-
- @Override
- public String toString() {
- return "Student{" +
- "studentName='" + studentName + '\'' +
- ", studentId=" + studentId +
- '}';
- }
-
- @Override
- public Student clone() {
- try {
- Student clone = (Student) super.clone();
- return clone;
- } catch (CloneNotSupportedException e) {
- throw new AssertionError();
- }
- }
- }
Teacher类clone方法重写
- package com.code.designpattern.prototype;
-
- import java.io.Serializable;
- import java.util.ArrayList;
- import java.util.List;
-
- public class Teacher implements Cloneable{
- private String teacherName;
- private int teacherId;
- private List<Student> students;
-
- public Teacher() {
- }
-
- public Teacher(String teacherName, int teacherId, List<Student> students) {
- this.teacherName = teacherName;
- this.teacherId = teacherId;
- this.students = students;
- }
-
- public String getTeacherName() {
- return teacherName;
- }
-
- public void setTeacherName(String teacherName) {
- this.teacherName = teacherName;
- }
-
- public int getTeacherId() {
- return teacherId;
- }
-
- public void setTeacherId(int teacherId) {
- this.teacherId = teacherId;
- }
-
- public List<Student> getStudents() {
- return students;
- }
-
- public void setStudents(List
students ) { - this.students = students;
- }
-
- @Override
- public String toString() {
- return "Teacher{" +
- "teacherName='" + teacherName + '\'' +
- ", teacherId=" + teacherId +
- ", students=" + students +
- '}';
- }
-
- @Override
- public Teacher clone() {
- try {
- // 重点在于改写这个clone方法,对非基本数据类型都需要调用clone方法重新赋值。
- Teacher clone = (Teacher) super.clone();
- List<Student> studentsList = clone.getStudents();
- List<Student> studentsList2 = new ArrayList<>();
- for (Student s:studentsList) {
- studentsList2.add(s.clone());
- }
- clone.setStudents(studentsList2);
- return clone;
- } catch (CloneNotSupportedException e) {
- throw new AssertionError();
- }
- }
- }
查看最终效果如下:
- public class PrototypeTest {
-
- public static void main(String[] args) throws Exception {
- Teacher teacher = new Teacher();
- teacher.setTeacherName("李明");
- teacher.setTeacherId(1001);
-
- List<Student> list = new ArrayList<>();
- list.add(new Student("小红", 230001));
- list.add(new Student("小亮", 230002));
- teacher.setStudents(list);
-
- Teacher newTeacher = teacher.clone();
- newTeacher.getStudents().get(0).setStudentName("小冰");
- System.out.println(newTeacher);
- System.out.println(teacher);
- }
- }
-
- // 测试输出如下:
- //Teacher{teacherName='李明', teacherId=1001,
- //students=[Student{studentName='小冰', studentId=230001},
- //Student{studentName='小亮', studentId=230002}]}
- //Teacher{teacherName='李明', teacherId=1001,
- //students=[Student{studentName='小红', studentId=230001},
- //Student{studentName='小亮', studentId=230002}]}
一般推荐使用序列化的方法进行深拷贝,实现Cloneable接口重写clone方法的方式有些繁琐。