设计模式之原型模式

什么是原型模式?

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

具体结构

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

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

相关推荐
秋意钟3 分钟前
Spring新版本
java·后端·spring
椰椰椰耶4 分钟前
【文档搜索引擎】缓冲区优化和索引模块小结
java·spring·搜索引擎
mubeibeinv6 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
青莳吖7 分钟前
Java通过Map实现与SQL中的group by相同的逻辑
java·开发语言·sql
Buleall14 分钟前
期末考学C
java·开发语言
重生之绝世牛码16 分钟前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式
小蜗牛慢慢爬行22 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
shinelord明42 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
新手小袁_J1 小时前
JDK11下载安装和配置超详细过程
java·spring cloud·jdk·maven·mybatis·jdk11
呆呆小雅1 小时前
C#关键字volatile
java·redis·c#