源码链接(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”)。