0
点赞
收藏
分享

微信扫一扫

【设计模式3】原型模式

weipeng2k 2022-01-16 阅读 65

基本介绍

  1. 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象;
  2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节;
  3. 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone();
  4. 形象的理解:孙大圣拔出猴毛, 变出其它孙大圣。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

原型模式:浅拷贝和深拷贝

示例:
在这里插入图片描述

  1. Prototype : 原型类,声明一个克隆自己的接口
  2. ConcretePrototype: 具体的原型类, 实现一个克隆自己的操作
  3. Client: 让一个原型对象克隆自己,从而创建一个新的对象(属性一样)

浅拷贝

浅拷贝的介绍
1)对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
2)对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
3)浅拷贝是使用默认的 clone()方法来实现
Sheep sheep = (Sheep) super.clone();
示例代码:

public class Sheep {

    private String name;
    private int age;
    private String color;
    public Sheep friend; 
    public Sheep(String name, int age, String color) {
        super();
        this.name = name;
        this.age = age;
        this.color = color;
    }
    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;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                ", friend=" + friend +
                '}';
    }

    //克隆该实例,使用默认的clone方法来完成
    @Override
    protected Object clone()  {

        Sheep sheep = null;
        try {
            sheep = (Sheep)super.clone();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return sheep;
    }

}

深拷贝

基本介绍
1)复制对象的所有基本数据类型的成员变量值。
2)为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。
3)深拷贝实现方式1:重写clone方法来实现深拷贝。
4)深拷贝实现方式2:通过对象序列化实现深拷贝(推荐).
示例代码:

public class DeepCloneableTarget implements Serializable, Cloneable{


    /**
     *序列化序号
     */
    private static final long serialVersionUID = 1L;

    private String cloneName;

    private String cloneClass;

    //构造器
    public DeepCloneableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    //因为该类的属性,都是String , 因此我们这里使用默认的clone完成即可
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
import java.io.*;

public class DeepProtoType implements Serializable, Cloneable{
    public String name; //String 属性
    public DeepCloneableTarget deepCloneableTarget;// 引用类型
    public DeepProtoType() {
        super();
    }


    //深拷贝 - 方式 1 使用clone 方法
    @Override
    protected Object clone() throws CloneNotSupportedException {

        Object deep = null;
        //这里完成对基本数据类型(属性)和String的克隆
        deep = super.clone();
        //对引用类型的属性,进行单独处理
        DeepProtoType deepProtoType = (DeepProtoType)deep;
        deepProtoType.deepCloneableTarget  = (DeepCloneableTarget)deepCloneableTarget.clone();

        // 弊端:如果存在多个引用对象,则每个引用对象都需要单独处理;
        return deepProtoType;
    }

    //深拷贝 - 方式2 通过对象的序列化实现 (推荐),
    //优势,引用对象(非基础类型和String)有多个,则对整个对象拷贝,处理简单;
    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 copyObj = (DeepProtoType)ois.readObject();

            return copyObj;

        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            return null;
        } finally {
            //关闭流
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (Exception e2) {
                System.out.println(e2.getMessage());
            }
        }
    }
}

原型模式的注意事项和细节

原型模式的注意事项和细节

  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率;
  2. 不用重新初始化对象,而是动态地获得对象运行时的状态;
  3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码;
  4. 在实现深克隆的时候可能需要比较复杂的代码;
  5. 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则,这点请同学们注意.

使用场景:

1、资源优化场景。

2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。

3、性能和安全要求的场景。

4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

5、一个对象多个修改者的场景。

6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。

7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

原型模式在 Spring 框架中的应用

在 Spring 中原型 bean 的创建,就是使用得原型设计模式,比如我们在配置 Spring 的 bean 的时候,有这么个标签, 而这个 scope 是用来区分你要设置的是单例的还是原型。
配置文件如下:

<bean id="sheep" class="com.java.springtest.prototype.Sheep" scope="prototype"/>

我们创建一个测试类来读取一下这个配置文件

import com.java.springtest.prototype.Sheep;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class PrototypeTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        Sheep sheep = (Sheep) applicationContext.getBean("sheep");
        System.out.println(sheep);
    }
}

为了更好地理解 Spring 是如何使用原型设计模式的,我们需要探究一下源码,我们在 getBean() 这行打个断点调试一下:
在这里插入图片描述
点击下一步的时候跳转到了 AbstractApplicationContext 类的 getBean() 方法
在这里插入图片描述
但是这还看不出是否使用了原型,继续往下执行
上面的图中通过 this.getBeanFactory().getBean(name) 调用了 getBean() 方法;
该 getBean() 方法是 AbstractBeanFactory 类的 public Object getBean(Stirng name) 方法。
在这里插入图片描述
从图中可以看到 getBean() 方法调用的是 doGetBean() 方法,继续执行跳转到了 doGetBean() 方法,该方法使用了泛型。
在这里插入图片描述
而我们要的东西就在这个方法里面,我们把代码拉到下面一点有这么一段代码
这段代码很容易理解,判断当前是否为单例,如果是则调用 createBean() 创建一个单例实例,但是我们在 bean 中定义的是 prototype 原型,所以代码并不会执行 if 里面的语句
在这里插入图片描述
而在 if 后面有个 else 语句,否则如果是原型的则调用 createBean() 创建一个原型的实例。
在这里插入图片描述

举报

相关推荐

0 条评论