设计模式(五):原型模式

Published on 2024-03-05 10:49 in 分类: 博客 with 狂盗一枝梅
分类: 博客

一、原型模式定义

Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.(用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。)

原型模式非常简单,核心思想就是复制要比从头开始创建效率高,生活中也有这种常见的例子:

你收到老板发过来的一份PDF,老板让你打印100份,你会怎么做?

正确的做法是先打印一份,然后使用打印出来的这一份复印99份,这是效率最高的做法;当然也可以直接选择打印99份,你将会听到打印机一直轰隆轰隆的工作,过了许久才会结束打印。

不仅仅是生活中的打印和复制案例,在JAVA的世界中也是如此,创建对象的效率要比复制对象的效率低的多,甚至JDK提供了Cloneable接口以方便对象克隆

image-20240305090845339

它的使用方式非常简单,首先,创建原型类,实现Cloneable接口

@Slf4j
public class Resume implements Cloneable{

    private String name;

    private String position;

    private String salary;
    
    @Override
    protected Object clone() {
        Resume resume = null;
        try{
            resume = (Resume) super.clone();
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return resume;
    }
}

在用的时候直接调用克隆方法

//使用原型模式
public class Client {
    public static void main(String[] args) {
        Resume resume = new Resume("小李", "海淀区", "面议");
        Resume resume1 = (Resume)resume.clone();
    }
}

二、原型模式的使用场景

1、在需要一个类的大量对象的时候,使用原型模式是最佳选择,因为原型模式是在内存中对这个对象进行拷贝,要比直接new这个对象性能要好很多,在这种情况下,需要的对象越多,原型模式体现出的优点越明显

2、如果一个对象的初始化需要很多其他对象的数据准备或其他资源的繁琐计算,那么可以使用原型模式拷贝

三、注意事项

1、构造函数可能不会被执行

仔细看这段代码:

@Override
protected Object clone() {
    Resume resume = null;
    try{
        resume = (Resume) super.clone();
    }catch (CloneNotSupportedException e){
        e.printStackTrace();
    }
    return resume;
}

这个clone方法是继承自Object的,super.clone方法调用super也指的是Object,Object的clone方法实际上就是从内存中进行的一个对象拷贝(浅拷贝),它不会执行构造函数。

2、clone方法和final关键字冲突

对象的clone与对象内的final关键字是有冲突的,两个只能存在一个,想想就明白了,final关键字修饰的字段是不可变的,不能赋值当然也就没法拷贝了。

3、深拷贝和浅拷贝

3.1 浅拷贝

对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,就是将该属性值复制一份给新的对象。

对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象,实际上两个对象的该成员变量都指向同一个实例,这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

Object类的clone方法就是浅拷贝方法。

3.2 深拷贝

复制对象的所有基本数据类型的成员变量值。

为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象,也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝。

深拷贝有两种常见实现方式:级联克隆对象序列化

级联克隆就是对于引用类型的对象类,也实现Cloneable接口,调用clone方法挨个克隆成员变量,很明显,这种方式很复杂,在成员变量很多,甚至成员变量内还有引用型变量的时候,变得尤为复杂;另外一种方式就是使用序列化和反序列化方法

public Object deepClone(){
    //创建流对象
    ByteArrayOutputStream bos = null;
    ObjectOutputStream oos = null;
    ByteArrayInputStream bis = null;
    ObjectInputStream ois = null;
    try {
        //序列化
        bos = new ByteArrayOutputStream();
        oos = new ObjectOutputStream(bos);
        oos.writeObject(this);//将当前对象,以对象流的方式输出(即序列化)
        //输出的时候会把包括引用类型一起输出
        //反序列化
        bis = new ByteArrayInputStream(bos.toByteArray());//把输出去的对象再读进来
        ois = new ObjectInputStream(bis);
        DeepProtoType clone = (DeepProtoType)ois.readObject();
        //充分利用序列化和反序列化的特点,把当前对象this以对象流的方式输出去,然后以对象的方式再读回来,关联的引用类型自然也读回来了
        return clone;
    } catch (Exception e) {
        e.getMessage();
    } finally {
        //关闭流操作
        try {
            bos.close();
            oos.close();
            bis.close();
            ois.close();
        } catch (IOException e) {
            e.getMessage();
        }
    }
    return null;
}

4、类必须实现Cloneable接口才能复制

clone方法是Object类的,那实不实现Cloneable接口是不是没啥影响,直接调用clone方法不就行了?

实际上如果不实现Cloneable接口,在调用Object类的clone方法的时候会抛出CloneNotSupportedException异常。

四、Spring中原型模式的误解

在Spring中有时候我们会这样写一段代码

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class DemoService{
    ......
}

@Scope是Spring框架中的注解,用于将一个类或组件的作用域设置为原型(Prototype),这样每次从Spring容器中获取DemoService的依赖注入时,都会创建一个新的DemoService实例。

那这代表Spring使用了原型模式创建了对象吗?

答案是否定的,原型模式是一种设计模式,用于创建对象的复制(克隆),以便在创建对象时避免使用new关键字。原型模式通过克隆现有对象来创建新对象,而不是通过构造函数创建新对象实例。在Spring框架中,尽管在prototype作用域下,每次请求对象都会创建一个新的实例,但这并不意味着该对象采用了原型模式。在Spring中,prototype作用域更侧重于对象的创建方式和对象的生命周期管理,而不是克隆现有对象来创建新对象。因此,将对象的作用域设置为prototype仅表示在每次请求对象时都会创建一个新的实例,而不是原型模式的具体实现。


#设计模式
目录
复制 复制成功