java设计模式-原型模式(Prototype)

121 阅读5分钟

「这是我参与2022首次更文挑战的第33天,活动详情查看:2022首次更文挑战

原型模式

在java中我们知道通过new关键字创建的对象是非常繁琐的(类加载判断,内存分配,初始化等),在我们需要大量对象的情况下,原型模式就是我们可以考虑实现的方式。   原型模式我们也称为克隆模式,即一个某个对象为原型克隆出来一个一模一样的对象,该对象的属性和原型对象一模一样。而且对于原型对象没有任何影响。原型模式的克隆方式有两种:浅克隆和深度克隆

原型模式说明
浅克隆只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝, 还是指向原生对象的内部元素地址
深度克隆深复制把要复制的对象所引用的对象都复制了一遍

浅拷贝

首先创建一个User类,其属性有基本数据类型,也有引用数据类型。如果需要克隆,该类需要继承Cloneable接口,并重写clone方法。

原型类

package com.jony.designpattern.prototype;

import java.util.Date;

public class User implements Cloneable {
    //基本数据类型
    private int age;
    //引用数据类型
    private String name;
    //引用数据类型
    private School school;

    public School getSchool() {
        return school;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    protected User clone() throws CloneNotSupportedException {
        return (User)super.clone();
    }

    @Override
    public String toString() {
        return super.hashCode()+"Use r{" +
                "name='" + name + ''' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

class School{
    private String schoolName;

    public String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }

    @Override
    public String toString() {
        return "School{" +
                "schoolName='" + schoolName + ''' +
                '}';
    }
}

浅拷贝测试方法

package com.jony.designpattern.prototype;

public class UserTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        School school=new School();
        school.setSchoolName("希望小学");
        User user=new User();
        user.setName("张三");
        user.setAge(10);
        user.setSchool(school);


        User user1=user.clone();
        System.out.println(user);
        System.out.println(user1);

        school.setSchoolName("腾达小学");
        user.setName("李四");
        user.setAge(20);

        System.out.println("----------------");
        System.out.println(user);
        System.out.println(user1);
    }
}

输出结果如下:

image.png

总结

分析结果可以验证:

基本数据类型是值传递,所以修改值后不会影响另一个对象的该属性值;

引用数据类型是地址传递(引用传递),所以修改值后另一个对象的该属性值会同步被修改。

String类型非常特殊,所以我额外设置了一个字符串类型的成员变量来进行说明。首先,String类型属于引用数据类型,不属于基本数据类型,但是String类型的数据是存放在常量池中的,也就是无法修改的!也就是说,当我将name属性从“张三”改为“李四"后,并不是修改了这个数据的值,而是把这个数据的引用从指向”张三“这个常量改为了指向”李四“这个常量。在这种情况下,另一个对象的name属性值仍然指向”张三“不会受到影响。

深拷贝

从上面的结果我们得知,User通过重写了clone,其基本数据类型因为是值传递,所以完成“深拷贝”,但是User中的属性School是一个对象,在user.clone的时候,无法对Scool也进行拷贝,因此,我们要把School也进行深度拷贝,代码修改如下:

原型类

package com.jony.designpattern.prototype;

import java.util.Date;

public class User implements Cloneable {
    //基本数据类型
    private int age;
    //引用数据类型
    private String name;
    //引用数据类型
    private School school;

    public School getSchool() {
        return school;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    protected User clone() throws CloneNotSupportedException {
        User user=(User)super.clone();
        user.school=user.getSchool().clone();
        return user;
    }

    @Override
    public String toString() {
        return super.hashCode()+"Use r{" +
                "name='" + name + ''' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

class School implements Cloneable{
    private String schoolName;

    public String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }

    @Override
    protected School clone() throws CloneNotSupportedException {
        return (School)super.clone();
    }

    @Override
    public String toString() {
        return "School{" +
                "schoolName='" + schoolName + ''' +
                '}';
    }
}

我们在User对象中的clone方法中,把其引用对象也进行同步克隆,这样就实现了深度克隆(如果有N个引用对象,就全部需要进行clone)

测试类

package com.jony.designpattern.prototype;

public class UserTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        School school=new School();
        school.setSchoolName("希望小学");
        User user=new User();
        user.setName("张三");
        user.setAge(10);
        user.setSchool(school);


        User user1=user.clone();
        System.out.println(user);
        System.out.println(user1);

        school.setSchoolName("腾达小学");
        user.setName("李四");
        user.setAge(20);

        System.out.println("----------------");
        System.out.println(user);
        System.out.println(user1);
    }
}

执行结果

image.png 通过以上结果,可以得知,User中引用的School,在值进行改变之后,克隆的对象未改变,这样我们就实现了深拷贝。

总结

虽然可以通过对引用对象进行同步拷贝来实现深度拷贝,但是如果有N个引用对象,就会无形中造成较多的编码量。因此我们可以将对象进行序列操作进行深拷贝。

序列化深拷贝

原型代码

去掉实现Cloneable,改为实现Serializable接口,同时去掉User和School中的clone方法

package com.jony.designpattern.prototype;

import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {
    private static final long serialVersionUID = -2546622010956625341L;
    //基本数据类型
    private int age;
    //引用数据类型
    private String name;
    //引用数据类型
    private School school;

    public School getSchool() {
        return school;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

//    @Override
//    protected User clone() throws CloneNotSupportedException {
//        User user=(User)super.clone();
//        user.school=user.getSchool().clone();
//        return user;
//    }

    @Override
    public String toString() {
        return super.hashCode()+"Use r{" +
                "name='" + name + ''' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

class School implements  Serializable {
    private static final long serialVersionUID = 2281697513295893290L;
    private String schoolName;

    public String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }

//    @Override
//    protected School clone() throws CloneNotSupportedException {
//        return (School)super.clone();
//    }

    @Override
    public String toString() {
        return "School{" +
                "schoolName='" + schoolName + ''' +
                '}';
    }
}

测试类

package com.jony.designpattern.prototype;

import java.io.*;

public class UserTest {
    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        School school=new School();
        school.setSchoolName("希望小学");
        User user=new User();
        user.setName("张三");
        user.setAge(10);
        user.setSchool(school);

        //通过序列进行深拷贝
        ByteArrayOutputStream bos=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(bos);
        oos.writeObject(user);
        oos.flush();
        ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        User user1=(User)ois.readObject();
        //通过Cloneable,并对引用类型进行clone实现深拷贝
//        User user1=user.clone();
        System.out.println(user);
        System.out.println(user1);

        school.setSchoolName("腾达小学");
        user.setName("李四");
        user.setAge(20);

        System.out.println("----------------");
        System.out.println(user);
        System.out.println(user1);
    }
}

执行结果

image.png

总结

优点:
可以通过很简洁的代码即可完美实现深拷贝。不过要注意的是,如果某个属性被transient(添加这个关键字,属性就不会被序列化了)修饰,那么该属性就无法被拷贝了。 缺点:
序列化相对来说在性能方面还是有些影响的,序列化操作属于CPU密集型,需要解析流,这种速度相对较慢,如果应用没有太复杂,或者对性能要求不是太高,可以考虑这种方案

以上是浅拷贝的深拷贝的区别和实现方式。