前言:
视频教程:狂神说Java之通俗易懂的23种设计模式
什么是设计模式?
- 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。
它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的
解决方案==> 一种思维,一种态度,一种进步- 1995年,GoF(Gang of Four,四人组/四人帮)合作出版了《设计模式:可复用面向对象软
件的基础》一书,共收录了23种设计模式,从此树立了软件设计模式领域的里程碑,人称 【GoF设计模式】
设计模式分类 具体模式 创建型模式:
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。⌛单例模式、⌛工厂模式、⌛抽象工厂模式、⌛建造者模式、⌛原型模式 结构型模式:
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式 行为型模式:
这些设计模式特别关注对象之间的通信。模板方法模、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
原型模式:
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
说人话,原型模式就是Java官方的cv方法(又称 克隆模式),其中要学会区别 深克隆与 浅克隆 的区别(也称作“浅拷贝”和“深拷贝”)
扩展阅读:
- 用故事讲述克隆模式:原型模式
- 图文结合: 深度好文:设计模式之——原型模式
代码:
实现步骤:
-
实现
Cloneable
接口 -
重写
clone()
方法,只有当一个类实现了Cloneable接口后,该类才会被赋予调用重写自Object类的clone方法得权利。否则会抛出“CloneNotSupportedException”异常。
举例bilibili视频
-
浅克隆:
- 当类的成员变量是基本数据类型时,浅拷贝会复制该属性的值赋值给新对象。
- 当成员变量是引用数据类型时,浅拷贝复制的是引用数据类型的地址值。这种情况下,当拷贝出的某一个类修改了引用数据类型的成员变量后,会导致所有拷贝出的类都发生改变。
默认实现父类的克隆方法都是浅克隆
Vlog对象
public class Vlog implements Cloneable {
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//省略对属性的get/set方法,与toString方法
}
客户端:
public class Bilibili {
public static void main(String[] args) throws CloneNotSupportedException {
//原型对象:
Date date = new Date();
Vlog v1 = new Vlog("Wayne的Vlog", date);
//v1克隆出v2
//Vlog v2 = new Vlog("Wayne的Vlog", date);
Vlog v2 = (Vlog) v1.clone();
System.out.println("v1==>"+v1);
System.out.println("v1=>hash==>"+v1.hashCode());
System.out.println("v2==>"+v2);
System.out.println("v2=>hash==>"+v2.hashCode());
//此时v1与v2属性是一样的
//由于hashCode不同,可以对值进行单独修改
v2.setName("盗版Vlog");
System.out.println("v2==>"+v2);
//但这时候将date对象属性改变
System.out.println("=====================改变时间=====================");
date.setTime(66666666L);
System.out.println("v1==>"+v1);
System.out.println("v2==>"+v2);
//这时候v1、v2两个实例的date属性将会一起改变
}
}
v1==>Vlog{name='Wayne的Vlog', createTime=Mon Mar 08 21:04:35 CST 2021}
v1=>hash==>1580066828
v2==>Vlog{name='Wayne的Vlog', createTime=Mon Mar 08 21:04:35 CST 2021}
v2=>hash==>491044090
v2==>Vlog{name='盗版Vlog', createTime=Mon Mar 08 21:04:35 CST 2021}
=====================改变时间=====================
v1==>Vlog{name='Wayne的Vlog', createTime=Fri Jan 02 02:31:06 CST 1970}
v2==>Vlog{name='盗版Vlog', createTime=Fri Jan 02 02:31:06 CST 1970}
-
深克隆
- 深拷贝不仅会复制成员变量为基本数据类型的值,给新对象。
- 还会给是引用数据类型的成员变量申请储存空间,并复制引用数据类型成员变量的对象。这样拷贝出的新对象就不怕修改了是引用数据类型的成员变量后,对其它拷贝出的对象造成影响了.
public class Vlog implements Cloneable {
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException {
Vlog vlog = (Vlog) super.clone();
vlog.setCreateTime((Date) this.getCreateTime().clone());
//或者直接给属性赋值也行:
//vlog.createTime = (Date) this.createTime.clone();
return vlog;
}
//省略对属性的get/set方法,与toString方法
}
这时再去客户端测试:
public class Bilibili {
public static void main(String[] args) throws CloneNotSupportedException {
//原型对象:
Date date = new Date();
Vlog v1 = new Vlog("Wayne的Vlog", date);
//v1克隆出v2
//Vlog v2 = new Vlog("Wayne的Vlog", date);
Vlog v2 = (Vlog) v1.clone();
v2.setName("盗版的Vlog");
System.out.println("v1==>"+v1);
System.out.println("v2==>"+v2);
//但这时候将date对象属性改变
System.out.println("=====================改变时间=====================");
date.setTime(66666666L);
System.out.println("v1==>"+v1);
System.out.println("v2==>"+v2);
}
}
v1==>Vlog{name='Wayne的Vlog', createTime=Mon Mar 08 21:24:19 CST 2021}
v2==>Vlog{name='盗版的Vlog', createTime=Mon Mar 08 21:24:19 CST 2021}
=====================改变时间=====================
v1==>Vlog{name='Wayne的Vlog', createTime=Fri Jan 02 02:31:06 CST 1970}
v2==>Vlog{name='盗版的Vlog', createTime=Mon Mar 08 21:24:19 CST 2021}
深/浅克隆-小结:
浅拷贝:
- 当类的成员变量是基本数据类型时,浅拷贝会复制该属性的值赋值给新对象。
- 当成员变量是引用数据类型时,浅拷贝复制的是引用数据类型的地址值。这种情况下,当拷贝出的某一个类修改了引用数据类型的成员变量后,会导致所有拷贝出的类都发生改变。
深拷贝:
-
深拷贝不仅会复制成员变量为基本数据类型的值,给新对象。
-
还会给是引用数据类型的成员变量申请储存空间,并复制引用数据类型成员变量的对象。这样拷贝出的新对象就不怕修改了是引用数据类型的成员变量后,对其它拷贝出的对象造成影响了。
-
模式优缺点分析
原型模式的优点:
- Java自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加高。
//源码直接调用native 方法 protected native Object clone() throws CloneNotSupportedException;
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),实现在Java层面的svn操作
原型模式的缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改其中的clone代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
- 克隆破坏单例
单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限(同上,直接调用native方法),所以,单例模式与原型模式是冲突的,在使用时要特别注意。其实防御方式很简单,单例类不要实现Cloneable接口即可。
应用场景:
-
ArrayList中clone()方法的源码
public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }
-
Spring中的原型模式:
在Spring中,用户也可以采用原型模式来创建新的Bean实例,从而实现每次获取的是通过克隆生成的新实例,对其进行修改时对原有实例对象不造成任何影响。
在每次使用对象之前,都会创建一个新的对象,并且会将依赖关系完整的赋值给这个新创建的对象。这样有利于节省系统资源,还可以更好的对原型管理器对象进行控制。
singleton:单例模式,Spring IoC容器中只会存在一个共享的Bean实例,无论有多少个Bean引用它,始终指向同一对象。Singleton作用域是Spring中的缺省作用域,也可以显示的将Bean定义为singleton模式,配置为:
<bean id="userDao" class="com.ioc.UserDaoImpl" scope="singleton"/>
prototype:原型模式,每次通过Spring容器获取prototype定义的bean时,容器都将创建一个新的Bean实例,每个Bean实例都有自己的属性和状态,而singleton全局只有一个对象。
根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。
request:在一次Http请求中,容器会返回该Bean的同一实例。而对不同的Http请求则会产生新的Bean,而且该bean仅在当前Http Request内有效。
<bean id="loginAction" class="com.cnblogs.Login" scope="request"/>
针对每一次Http请求,Spring容器根据该bean的定义创建一个全新的实例,且该实例仅在当前Http请求内有效,而其它请求无法看到当前请求中状态的变化,当当前Http请求结束,该bean实例也将会被销毁。
session:在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。
<bean id="userPreference" class="com.ioc.UserPreference" scope="session"/>
同Http请求相同,每一次session请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的session请求内有效,请求结束,则实例将被销毁。
global Session:在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效。