设计模式之原型模式

什么是原型模式?

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

具体结构

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

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

实现方法

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

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

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

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

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

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<Student> 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<Student> 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方法的方式有些繁琐。

相关推荐
StayInLove2 分钟前
G1垃圾回收器日志详解
java·开发语言
对许6 分钟前
SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder“
java·log4j
无尽的大道10 分钟前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
小鑫记得努力19 分钟前
Java类和对象(下篇)
java
binishuaio23 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE25 分钟前
【Java SE】StringBuffer
java·开发语言
老友@25 分钟前
aspose如何获取PPT放映页“切换”的“持续时间”值
java·powerpoint·aspose
wrx繁星点点40 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
Upaaui43 分钟前
Aop+自定义注解实现数据字典映射
java
zzzgd81643 分钟前
easyexcel实现自定义的策略类, 最后追加错误提示列, 自适应列宽,自动合并重复单元格, 美化表头
java·excel·表格·easyexcel·导入导出