Java23种设计模式之原型模式的学习

源码链接(Gitee码云):https://gitee.com/oldou/javadesignpatterns
这里有我整理好的Java23种设计模式的源码以及博客教程,博客教程中介绍了Java23种设计的模式的各种实现方式以及应用场景,非常适用于学习以及提高我们的设计思维,如果对大家有所帮助,请记得star一下给予作者一定的精神支持,你的star是我写出更好的博客的动力,谢谢大家。

原型模式(prototype)

介绍

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

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

  • 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  • 就是java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点
  • 优势有:效率高(直接克隆,避免了重新执行构造过程步骤) 。
  • 克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的对象的属性值完全和原型对象相同。并且克隆出的新对象改变不会影响原型对象。然后,再修改克隆对象的值。

原型模式实现

  • 通过实现Cloneable接口和重写clone方法实现
  • Prototype模式中实现起来最困难的地方就是内存复制操作,所幸在Java中提供了clone()方法替我们做了绝大部分事情

分类

原型模式的核心就是拷贝对象,那么我们能拷贝一个对象实例的什么内容呢?这就要区分深拷贝和浅拷贝之分了。

(1)浅拷贝:我们只拷贝对象中的基本数据类型(8种),对于数组、容器、引用对象等都不会拷贝。

  • 浅克隆存在的问题
    被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。

(2)深拷贝:不仅能拷贝基本数据类型,还能拷贝那些数组、容器、引用对象等;

  • 深克隆如何实现?
    深克隆把引用的变量指向复制过的新对象,而不是原有的被引用的对象。
    深克隆:让已实现Clonable接口的类中的属性也实现Clonable接口;
    基本数据类型和String能够自动实现深度克隆(值的复制)。

下面我们用代码分别实现一下浅克隆和深克隆。

浅克隆的实现方式

第一步:定义原型类

/** 原型模式(浅克隆)
 * 第一步:实现 Cloneable 接口
 * 第二步: 重写一个方法---clone()
 */
public class Video implements Cloneable { //视频的原型

    private String name;//视频的名字
    private Date createTime;//视频的发布日期

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

    public Video() {
    }
    public Video(String name, Date createTime) {
        this.name = name;
        this.createTime = createTime;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Date getCreateTime() {
        return createTime;
    }
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
    @Override
    public String toString() {
        return "Video{" +
                "name='" + name + '\'' +
                ", createTime=" + createTime +
                '}';
    }
}

客户端:

/**
 * 客户端:克隆别人的视频
 */
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        //首先创建一个原型对象
        Date date = new Date();
        Video v1 = new Video("Java入坑指南",date);

        System.out.println("v1的信息---->"+v1);
        System.out.println("v1的HashCode为:"+v1.hashCode());

        //通过克隆v1创建出一个v2对象
        Video v2 = (Video)v1.clone();
        System.out.println("v2的信息---->"+v2);
        System.out.println("v2的HashCode为:"+v2.hashCode());

    }
}

首先我们通过Object的克隆方法将v1克隆出v2对象,同时对比一下v1和v2
在这里插入图片描述
我们可以看见,基本信息是一模一样的,但是HashCode不一样,可以说明这是两个不一样的对象,但是这样的克隆是有问题的,我们以上代码总共有三个对象,v1、v2和date对象,关系图如下所示:
在这里插入图片描述
我们使用以上方式克隆被称为浅克隆,克隆出来的v2与v1指向了同一个date,下面我们通过代码测试一下:

//首先创建一个原型对象
Date date = new Date();
Video v1 = new Video("Java入坑指南",date);
Video v2 = (Video)v1.clone();
System.out.println("v1的信息---->"+v1);
System.out.println("v2的信息---->"+v2);

System.out.println("-------------------------------");
date.setTime(22322121);//随意修改一下时间,说明时间是改变了
System.out.println("v1的信息---->"+v1);
System.out.println("v2的信息---->"+v2);

输出:
在这里插入图片描述
从输出结果可以证明,v1和v2同时指向了同一个值date,这就意味着我们只是将对象的值全部给拷贝过来了,同时也包括了对象的引用,所以说指向了同一个date,这就是浅克隆的问题。但这并不是我们想要的结果,我们想要的结果就是克隆过来之后拥有属于自己的引用。

深克隆的实现

如下图所示:在这里插入图片描述
我们想要克隆出来的对象拥有一个自己的Date对象,改变v1的Date时不会改变v2的Date,实现深克隆我们需要去改造一下clone()方法,将对象进行克隆的同时也将该对象的属性进行克隆。
下面我们来用代码实现一下深克隆:

/** 原型模式(深克隆)
 * 第一步:实现 Cloneable 接口
 * 第二步: 重写一个方法---clone(),同时将该方法进行改造,将克隆对象的属性也进行克隆
 */
public class Video implements Cloneable { //视频的原型
..................
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object obj = super.clone();
        //实现深克隆
        Video v = (Video) obj;
        //将这个对象的属性也进行克隆
        v.createTime = (Date) this.createTime.clone();
        
        return obj;
    }

 .............
}

还是原来的客户端去测试一下:
在这里插入图片描述
我们可以发现改变v1的日期不会改变v2的日期了,这就说明通过深克隆创建的对象,即使改变原型对象的属性也不会影响克隆对象。

实现深克隆的方式还有序列化和反序列化,但是由于都是操作IO流,所以效率相对而言会比较低。这里就不进行测试了。

原型模式的应用场景

  • 原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
    例如:spring中bean的创建实际就是两种:单例模式和原型模式。(当然,原型模式需要和工厂模式搭配起来)
  • 短时间大量创建对象时,原型模式和普通new方式效率测试
  • javascript语言中的继承怎么实现?那里面也有prototype。
  • Spring 中,@Service 默认都是单例的。用了私有全局变量,若不想影响下次注入或每次上下文获取 bean,就需要用到原型模式,我们可以通过以下注解来实现,@Scope(“prototype”)。