设计模式之原型模式

什么是原型模式?

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

具体结构

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

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

相关推荐
IU宝3 分钟前
C/C++内存管理
java·c语言·c++
瓜牛_gn3 分钟前
依赖注入注解
java·后端·spring
hakesashou4 分钟前
Python中常用的函数介绍
java·网络·python
佚先森13 分钟前
2024ARM网络验证 支持一键云注入引流弹窗注册机 一键脱壳APP加固搭建程序源码及教程
java·html
小白不太白95017 分钟前
设计模式之 责任链模式
python·设计模式·责任链模式
古月居GYH27 分钟前
在C++上实现反射用法
java·开发语言·c++
何大春44 分钟前
【弱监督语义分割】Self-supervised Image-specific Prototype Exploration for WSSS 论文阅读
论文阅读·人工智能·python·深度学习·论文笔记·原型模式
吾与谁归in1 小时前
【C#设计模式(13)——代理模式(Proxy Pattern)】
设计模式·c#·代理模式
吾与谁归in1 小时前
【C#设计模式(14)——责任链模式( Chain-of-responsibility Pattern)】
设计模式·c#·责任链模式
闲人一枚(学习中)1 小时前
设计模式-创建型-原型模式
设计模式