设计模式
1. 设计模式介绍
1.1 设计模式是什么?
设计模式是一套被 反复使用、多数人知晓、经过分类编目的、代码设计经验的总结。
1.2 为什么使用设计模式?
为了 可重用 代码,让代码更容易的被他人理解并保证代码的可靠性。
1.3 谁设计的设计模式?
四人组( Gang of Four ),简称 GoF ,分别是 Erich Gamma, Richard Helm , Ralph Johnson 和 John Vlissides 。但不是他们设计的,是他们在 95年的时候整理归纳 23 种最常用的设计模式并会变成一本书 Design Patterns: Elements of Reusable Object-Oriented Software 就是:《设计模式:可复用面向对象软件的基础》 。是经过时间的验证的,大家一定要学会。
1.4 设计模式怎么学?
设计模式最重要的 4 个元素 模式名称 (Parttern Name)、 问题(Problem) 、 解决方案 (Solution) 、 效果 (Consequences)。
学习设计模式我们就从这 4 个元素出发。一个一个弄清楚它。
-
模式名称 (Parttern Name)
就是这个模式的名字。简单明了的命名。
-
问题 (Problem)
描述了应该在何时使用模式,它包含了设计中存在的问题以及问题存在的原因
-
解决方案 (Solution)
该框架的解决方案。就是如何设计这个模式。描述了一个设计模式的组成成分,以及这些组成成分之间的相互关系,各自的职责和协作方式,通常解决方案通过UML类图和核心代码来进行描述。
-
效果 (Consequences)
描述了模式的优缺点以及在使用模式时应权衡的问题 (呈现)
我们要掌握的应该除了GoF 说的 23 种模式还有加一个 简单工厂模式 (Simple Factory Pattern)
1.5 设计模式有哪些?
看图得知,这是按照用途分为三种: 创建型(Creational, 结构型(Structural, 行为型(Behavioral 三种。
创建型模式 Creational Pattern
- 单例模式 Singleton Pattern
- 简单工厂模式 Simple Factory Pattern
- 工厂方法模式 Factory Method Pattern
- 抽象工厂模式 Abstract Factory Pattern
- 原型模式 Prototype Pattern
- 建造者模式 Builder Pattern
结构型模式 Structural Pattern
- 适配器模式 Adapter Pattern
- 桥接模式 Bridge Pattern
- 组合模式 Composite Pattern
- 装饰模式 Decorator Pattern
- 外观模式 Façade Pattern
- 享元模式 Flyweight Pattern
- 代理模式 Proxy Pattern
行为型模式 Behavioral Pattern
- 职责链模式 Chain of Responsibility Pattern
- 命令模式 Command Pattern
- 解释器模式 Interpreter Pattern
- 迭代器模式 Iterator Pattern
- 中介者模式 Mediator Pattern
- 备忘录模式 Memento Pattern
- 观察者模式 Observer Pattern
- 状态模式 State Pattern
- 策略模式 Strategy Pattern
- 模板方法模式 Template Method Pattern
- 访问者模式 Visitor Pattern
2. 代理模式
2.1 什么是代理模式?
代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理类的创建时期,代理类可分为两种。
分类:
- 静态代理类:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
- 动态代理类:在程序运行时,运用反射机制动态创建而成。
- 还有一种动态代理CGLIB:代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。
2.2 代理模式的关键信息
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
应用实例:
- Windows 里面的快捷方式。
- 买火车票不一定在火车站买,也可以去代售点。
- 一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
- spring aop。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
**静态代理和动态代理的区别: **
- 静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
- 静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
- 动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过Proxy里的newProxyInstance得到代理对象。
2.2 静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
2.2.1 案例分析
具体要求:
1)定义一个接口: Iteacher Dao
2)目标对象 Teacherdao实现接口 Iteacher DAO
3)使用静态代理方式,就需要在代理对象 Teacherdaoproxy中也实现 Iteacher DAO
4)调用的时候通过调用代理对象的方法来调用目标对象。
5)特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
ITeacherDao
public interface ITeacherDao {
void teach();
}
TeacherProxyDao
public class TeacherProxyDao implements ITeacherDao {
private ITeacherDao targetTeacherDao;
public TeacherProxyDao(ITeacherDao targetTeacherDao) {
this.targetTeacherDao = targetTeacherDao;
}
public TeacherProxyDao() {
}
public void teach() {
System.out.println("代理开始。。。可以执行一些操作 eg:验证数据的正确性等");
targetTeacherDao.teach();
System.out.println("代理结束。。。");
}
}
TeacherDao
public class TeacherDao implements ITeacherDao{
public void teach() {
System.out.println("老师授课。。。。");
}
}
测试
@Test
public void staticProxyTest(){
//获取目标对象
TeacherDao teacherDao = new TeacherDao();
//获取代理对象,将目标对象传给代理对象
TeacherProxyDao teacherProxyDao = new TeacherProxyDao(teacherDao);
//使用代理对象调用方法
teacherProxyDao.teach();
}
2.2.2 静态代理模式的优缺点
优点:静态代理模式可以通过代理对象实现对目标对象功能的扩展,把一些繁琐重复的功能提取出来,目标对象中只保留核心的功能
缺点:静态代理类要在编译时期和目标代理类实现同一个接口,这会导致代理类非常多,且一旦借口要添加功能,代理类和目标类都需要被维护。
2.3 动态代理
2.3.1 什么是动态代理?
动态代理也叫做:JDK 代理、接口代理。代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象
2.3.2 JDK中动态代理所使用的API
-
代理类所在包:jav.lang.reflct.Proxy
-
JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClasLoader loader, Clas<?>[] interfaces,InvocationHandler h )
2.3.3 动态代理案例
以静态代理为例
思路图解:
ITeacherDao
public interface ITeacherDao {
void teach();
}
TeacherDao
public class TeacherDao implements ITeacherDao {
public void teach() {
System.out.println("老师授课。。。。");
}
}
TeacherProxyFactory
public Object getProxyInstance(){
return Proxy.newProxyInstance(targetDao.getClass().getClassLoader(), targetDao.getClass().getInterfaces()
, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy:就是返回的实例
System.out.println("代理开始。。。");
//使用反射调用方法
Object returnValue = method.invoke(targetDao, args);
System.out.println("代理提交。。。");
return returnValue;
}
});
}
Client
public class Client {
public static void main(String[] args) {
ITeacherDao teacherDao = new TeacherDao();
TeacherProxyFactory teacherProxyFactory = new TeacherProxyFactory(teacherDao);
ITeacherDao proxyInstance = (ITeacherDao) teacherProxyFactory.getProxyInstance();
proxyInstance.teach();
//proxyInstance -> class com.sun.proxy.$Proxy0 $表示代理对象
System.out.println("proxyInstance -> "+ proxyInstance.getClass());
}
}
2.4 cgLib代理
2.4.1 什么是cglib代理
- 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实
现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理 - Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代
理归属到动态代理。 - Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 jav 类与实现 jav 接口.它广泛的被许多 AOP 的
框架使用,例如 Spring AOP,实现方法拦截 - 在 AOP 编程中如何选择代理模式:
- 目标对象需要实现接口,用 JDK 代理
- 目标对象不需要实现接口,用 Cglib 代理
- Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
2.4.2 cgLib代理实现步骤
**案例: **对TeacherDao实现cglib代理
图解思路
-
引入如下包
<dependencies> <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm --> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>7.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm-commons --> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-commons</artifactId> <version>7.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm-tree --> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-tree</artifactId> <version>8.0.1</version> </dependency> <!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> </dependencies>
-
实现目标类,不需要继承接口
public class TeacherDao { public void teach() { System.out.println("老师授课。。。。"); } }
-
创建代理工厂
- 代理工厂持有目标对象的引用
- 实现getProxyInstance方法
- 继承MethodInterceptor接口,实现intercept方法
public class TeacherProxyFactory implements MethodInterceptor { private final TeacherDao targetDao; public TeacherProxyFactory(TeacherDao teacherDao) { this.targetDao = teacherDao; } public Object getProxyInstance(){ //创建一个工具类 Enhancer enhancer = new Enhancer(); //指定父类对象 enhancer.setSuperclass(targetDao.getClass()); //设置回调函数 enhancer.setCallback(this); //创建目标对象的代理对象,即目标对象的子类对象 return enhancer.create(); } //反射生成的子类调用方法必定会被这个方法拦截,对目标对象方法的功能进行扩展 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("代理开始...."); Object returnValue = method.invoke(targetDao, objects); System.out.println("代理提交。。。"); return returnValue; } }
-
实现client类
public class Client { public static void main(String[] args) { //获取目标对象 TeacherDao teacherDao = new TeacherDao(); //获取代理工厂对象 TeacherProxyFactory proxyFactory = new TeacherProxyFactory(teacherDao); //获取代理对象 TeacherDao proxyInstance = (TeacherDao) proxyFactory.getProxyInstance(); proxyInstance.teach(); } }
2.4.3 cglib代理和普通的动态代理的区别
- cglib代理不需要实现接口
- cglib代理生成的对象是目标对象的子类而动态代理生成的是接口的实现类