JAVA设计模式
设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计 经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问 题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设 计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性
1. 学习设计模式的意义
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性 和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点。
1.可以提高程序员的思维能力、编程能力和设计能力。
2.使程序设计更加标准化、代码编制更加工程化,使开发效率大大提高, 从而缩短软件的开发周期。
3.使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
2. java设计模式类型
根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型 模式和行为型模式 3 种。
创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使 用分离”。提供了单例、原型、工厂方法、抽象工厂、建造者 5 种创建型模式。
结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,提供了代理、 适配器、桥接、装饰、外观、享元、组合 7 种结构型模式。
行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独 完成的任务,以及怎样分配职责。提供了模板方法、策略、命令、职责链、状态、 观察者、中介者、迭代器、访问者、备忘录、解释器 11 种行为型模式。
3 .23种设计模式介绍
- 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局 访问点供外部获取该实例,其拓展是有限多例模式。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆 出多个和原型类似的新实例。
- 工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子 类决定生产什么产品。
- 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个 子类可以生产一系列相关的产品。
- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客 户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口, 使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组 合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合 度。
- 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功 能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子 系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复 用。
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象 和组合对象具有一致的访问性。
- 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算 法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定 义该算法的某些特定步骤。
- 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它 们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下 一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能 力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改 变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互 关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个 元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对 象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的 解释方法,即解释器。
4. 常用设计模式
4.1 单例模式
在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求
只能创建一个实例,这就是所谓的单例模式. 例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,
或出现各个窗口显示内容的不一致等错误。
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点;
单例模式的结构
单例类:包含一个实例且能自行创建这个实例的类。
访问类:使用单例的类。其结构如图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uh0ePBgP-1678863513732)(C:\Users\封纪元\AppData\Roaming\Typora\typora-user-images\1642553736547.png)]
概括:
在整个程序中只允许有一个对象.
创建对象 使用构造方法. 将构造方法私有化,
只能在本类中创建,这样我们就可以控制数量.
向外界提供一个公共的访问方法.
总结:
Singleton 模式通常有两种实现形式。
第 1 种:懒汉式单例
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法 时才去创建这个单例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LrQDXzb8-1678863513734)(C:\Users\封纪元\AppData\Roaming\Typora\typora-user-images\1642555768240.png)]
第 2 种:饿汉式单例
该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance方法之前单例已经存在了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q2rLVrTL-1678863513736)(C:\Users\封纪元\AppData\Roaming\Typora\typora-user-images\1642555332065.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WNC5rUun-1678863513738)(C:\Users\封纪元\AppData\Roaming\Typora\typora-user-images\1642555369805.png)]
4.2 工厂模式(Factory Pattern)
工厂模式的定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工 作推迟到工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。
按实际业务场景划分,工厂模式有 2 种不同的实现方式,分别是
简单工厂模式、
抽象工厂模式。
简单工厂
我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创 建的产品不多,只要一个工厂类就可以完成,这种模式叫**“简单工厂模式”**。
在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模 式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。
简单工厂模式的主要角色如下:
简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例 的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品 对象。
抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
具体产品(ConcreteProduct):是简单工厂模式的创建目标。
其结构图如下图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eoltJdL6-1678863513739)(C:\Users\封纪元\AppData\Roaming\Typora\typora-user-images\1642554876211.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z53WLn5p-1678863513740)(C:\Users\封纪元\AppData\Roaming\Typora\typora-user-images\1642556062601.png)]
一个工厂模式代码:
package com.ffyc.javaforward.javadesign.simplefactory;
//抽象产品
public interface Product {
void show();
}
package com.ffyc.javaforward.javadesign.simplefactory;
//具体产品1
public class ProductA implements Product {
@Override
public void show() {
System.out.println("具体产品1显示...");
}
}
package com.ffyc.javaforward.javadesign.simplefactory;
//具体产品2
public class ProductB implements Product {
@Override
public void show() {
System.out.println("具体产品2显示...");
}
}
package com.ffyc.javaforward.javadesign.simplefactory;
/*
* 工厂,负责生产对象
*/
public class SimpleFactory {
//创建具体产品
public Product createProduct(String className){
if(className == null){
return null;
}else{
try {
return (Product) Class.forName(className).newInstance(); //利用反射机制
} catch (InstantiationException e) {
e.printStackTrace();
return null;
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
}
package com.ffyc.javaforward.javadesign.simplefactory;
public class Test {
public static void main(String[] args) {
SimpleFactory simpleFactory = new SimpleFactory();
Product productA = simpleFactory.createProduct("com.ff.javadesign.simplefactory.ProductA");
Product productB = simpleFactory.createProduct("com.ff.javadesign.simplefactory.ProductB");
}
}
4.3 代理模式
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一 个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要 去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找保姆、找工 作,买东西等都可以通过找中介完成。
代理模式的主要优点有:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作 用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ab2Q8KI7-1678863513741)(C:\Users\封纪元\AppData\Roaming\Typora\typora-user-images\1642562968459.png)]
模式的结构
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现 的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象 所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题 的引用,它可以访问、控制或扩展真实主题的功能。 其结构图如图所示
例:
代理—>代理商 4s店,中介, 商城
用户 ----> 汽车厂
用户 —> 大众4s店 (代理) 买保险,上牌照 —> 汽车厂
静态代理
静态代理一般使用于关系是固定的,代理某类事务必须实现接口,
如果需要代理多个目标对象,那么就需要实现更多的接口,后期维护比较麻烦.
如:一个代理类代码
Dao接口,定义保存功能
package javadesign.proxy.staticproxy;
/*
Dao接口,定义保存功能
*/
public interface BaseDao {
void save();
}
实际功能实现类
package javadesign.proxy.staticproxy;
/*
实际功能实现类
*/
public class UserDaoImpl implements BaseDao {
@Override
public void save() {
System.out.println("UserDaoImpl:save()");
}
}
静态代理类
package javadesign.proxy.staticproxy;
/*
* 静态代理类
*/
public class StaticDaoProxy implements BaseDao {
//接收所有实现BaseDao接口的实现类对象
private BaseDao baseDao;
// 将被代理者的实例传进动态代理类的构造函数中
public StaticDaoProxy(BaseDao baseDao) {
this.baseDao = baseDao;
}
//代理他们实现功能,可以在调用前,调用后额外添加功能.
@Override
public void save() {
System.out.println("before");
baseDao.save();
System.out.println("after");
}
}
测试类
package javadesign.proxy.staticproxy;
public class Test {
public static void main(String[] args) {
//把实际执行者交给代理对象管理即可
StaticDaoProxy subject = new StaticDaoProxy(new UserDaoImpl());
subject.save();
}
}
动态代理
动态代理中,代理类并不是在 Java 代码中实现,而是在运行时期生成,相 比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法 调用次数、添加日志功能等等,动态代理分为 jdk 动态代理和 cglib 动态代理
jdk 代理
动态代理的实现方式,是通过反射来实现的,借助 Java 自带的 java.lang.reflect.Proxy,通过固定的规则生成
其步骤如下:
- 编写一个委托类的接口,即静态代理的
- 实现一个真正的委托类,即静态代理的
- 创建一个动态代理类,实现 InvocationHandler 接口,并重写该 invoke 方法
- 在测试类中,生成动态代理的对象
一个jdk代理实例:
Dao接口,定义保存功能
package javadesign.proxy.dynamicproxy.jdk;
/*
Dao接口,定义保存功能
*/
public interface BaseDao {
void save();
}
实际功能实现类
package javadesign.proxy.dynamicproxy.jdk;
/*
实际功能实现类
*/
public class UserDaoImpl implements BaseDao {
@Override
public void save() {
System.out.println("UserDaoImpl:save()");
}
}
动态代理类
package javadesign.proxy.dynamicproxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/*
* 动态代理类
*/
public class DynamicDaoProxy implements InvocationHandler {
// 被代理类的实例
private Object object;
// 将被代理者的实例传进动态代理类的构造函数中
public DynamicDaoProxy(Object object) {
this.object = object;
}
/*
* 覆盖InvocationHandler接口中的invoke()方法
* Object proxy 表示代理对象
* Method method 代理对象中的方法
* Object[] args 表示代理方法中的参数
* 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构
* 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到
* 控制被代理对象的行为,下面的before、after就是我们可以进行特殊
* 代码切入的扩展点了。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = method.invoke(object, args);
System.out.println("after");
return result;
}
}
测试
package javadesign.proxy.dynamicproxy.jdk;
import com.ff.javadesign.proxy.dynamicproxy.cglib.UserDaoImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
//我们要代理的真实对象
UserDaoImpl userDaoImpl = new UserDaoImpl();
//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler dynamicProxy = new DynamicDaoProxy(userDaoImpl);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的 ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实 对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数dynamicProxy, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对 象上
*/
BaseDao baseDao =(BaseDao)Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader(),userDaoImpl.getClass().getInterfaces(), dynamicProxy);
baseDao.save();
}
}
简述:
jdk代理实现原理使用的是java反射机制,可以动态获取目标类中代理的方法,
不需要代理类指定的去实现某些抽象接口, 代理的扩展性好.
动态生成代理对象, 要求目标类必需有实现接口.
**动态代理总结:**虽然相对于静态代理,动态代理大大减少了我们的开发任务, 同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾 之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注 定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定 有一个共同的父类叫 Proxy。
Cglib代理
JDK 实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何 实现动态代理呢,这就需要 CGLib 了。CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦 截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对 final 修饰的类进行代理。JDK 动态代理与 CGLib 动态代理均是实现 Spring AOP 的基础
Cglib 子类代理实现方法:
1.需要引入 cglib 的 jar 文件,但是 Spring 的核心包中已经包括了 Cglib 功能,所
以直接引入 spring-core-xxx.jar 即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为 final,否则报错
4.目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额
外的业务方法.
Cglib示例:
package javadesign.proxy.dynamicproxy.cglib;
//具体主题
public class UserDaoImpl {
public void save() {
System.out.println("UserDaoImpl:save()");
}
}
package javadesign.proxy.dynamicproxy.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/*
* 动态代理类
*/
public class CGLibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class<?> clazz){
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
/*
* 拦截所有目标类方法的调用
* 参数:
* obj 目标实例对象
* method 目标方法的反射对象
* args 方法的参数
* proxy 代理类的实例
*/
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
//代理类调用父类的方法
System.out.println("开始事务");
Object obj1 = proxy.invokeSuper(obj, args);
System.out.println("关闭事务");
return obj1;
}
}
package javadesign.proxy.dynamicproxy.cglib;
public class Test {
public static void main(String[] args) {
CGLibProxy proxy = new CGLibProxy();
UserDaoImpl userDaoImpl = (UserDaoImpl) proxy.getProxy(UserDaoImpl.class);
userDaoImpl.save();
}
}
Cglib简述:
- 动态字节码技术
- 可以在运行中为目标类动态生成一个子类, 进行方法拦截,从而添加增强功能.
- 不能代理final所修饰的类, 目标类可以不实现任何接口.
- CGLIB 创建的动态代理对象比JDK 创建的动态代理对象的性能更高,但是 CGLIB 创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,因为无 需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。同时由 于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的方法无法进行代理。
动态代理总结【重要】
JDK 动态代理与 CGLib 动态代理均是实现 Spring AOP 的基础
spring中两种实现都支持 可以根据目标类是否实现接口,自动选择动态方式.