【设计模式(12)】结构型模式之代理模式
个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道
如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充
前言
“中介”这个职业大家都知道,比如买房租房,我们一般是与中介进行交流、看房、议价等等,甚至我上一次租的房住了一年都没见过房东,同理房东也没见过我。。。
还有譬如买火车票并不需要去火车站,在携程或者12306app上就可以购买;订酒店通过美团或者携程也能够完成,并不需要本人去酒店;还有浏览那些“不存在”的网站的时候,我们需要“梯子“等等
以租房中介来说,一方面他是我的中介,代替我去跟房东谈价格谈房屋配置,谈妥之前房东并不知道我是谁,相当于我的代理。
另一方面他也是房东的中介,代替房东跟我谈价格谈合同,谈妥之前房东同样不知道我是谁,相当于房东的代理。
而且在代理的过程中,并不一定会原话转达给对方,大概率会做一些小的变动,以促成合同的完成。这是他的能力范围内的事情,当然会怎么做看个人咯。
说到底,之所以需要中介,是因为我们不想或者不能直接访问目标对象。
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。
这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
1.介绍
使用目的:为其他对象提供一种代理以控制对这个对象的访问
使用时机:需要在访问一个类时添加一些控制
解决问题:由于某些原因客户端不能或者不想要直接访问目标对象
实现方法:添加中间层,用于实现与被代理类组合。客户端访问中间层,以间接访问被代理类
应用实例:
- Windows系统的快捷方式
- 机票、车票、酒店等代售点
- 房屋中介、职务中介等
- spring中的aop(面向切面编程)
优点:
- 客户端不能直接访问目标对象,可以保护目标对象,提高安全性
- 客户端与目标对象解耦,职责清晰,也提高了程序的扩展性
- 代理可以扩展目标对象的功能
缺点:
- 代理模式需要在系统中新增类,提高了系统体积
- 客户端和目标对象中增加了代理对象,会一定程度上降低执行速度
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂
使用场景:按职责来划分,通常有以下使用场景
说来话长,不多赘述
- 远程代理
- 虚拟代理
- Copy-on-Write 代理
- 保护(Protect or Access)代理
- Cache代理
- 防火墙(Firewall)代理
- 同步化(Synchronization)代理
- 智能引用(Smart Reference)代理
2.结构
代理模式的主要角色如下
-
抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
-
真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
-
代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
-
代理类和真实主题类都需要实现接口抽象主题类,以保证其对外接口一致
-
客户端调用代理类中的接口,实际上是对真实主题类中方法加上前置后置处理后的方法,但仍然需要调用真实主题类的方法
-
代理类需要持有一个真实主题类对象,以调用其中的目标方法
3.实现步骤
-
定义抽象主题接口
interface Subject { void Request(); }
-
定义真实主体类,实现抽象主题接口
class RealSubject implements Subject { @Override public void Request() { System.out.println("访问真实主题方法..."); } }
-
定义代理类,实现抽象主题接口,并持有一个真实主题对象
class Proxy implements Subject { private RealSubject realSubject; public Proxy() { this.realSubject = new RealSubject(); } @Override public void Request() { preRequest(); realSubject.Request(); postRequest(); } private void preRequest() { System.out.println("访问真实主题之前的预处理。"); } private void postRequest() { System.out.println("访问真实主题之后的后续处理。"); } }
需要持有一个真实主题对象,构造这个对象的时间可以按照业务处理,一般有下面3种情况
- 写在代理类构造函数中,构造代理类的同时构造真实主题对象
- 写在接口中,调用接口时检查是否已构造,没有则此时构造
- 在客户端中构造真实主题对象,通过代理类的构造函数传入代理类
完整代码
package com.company.test.proxy;
//抽象主题
interface Subject {
void Request();
}
//真实主题
class RealSubject implements Subject {
@Override
public void Request() {
System.out.println("访问真实主题方法...");
}
}
//代理
class Proxy implements Subject {
private RealSubject realSubject;
public Proxy() {
this.realSubject = new RealSubject();
}
@Override
public void Request() {
preRequest();
realSubject.Request();
postRequest();
}
private void preRequest() {
System.out.println("访问真实主题之前的预处理。");
}
private void postRequest() {
System.out.println("访问真实主题之后的后续处理。");
}
}
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.Request();
}
}
运行结果
4.正向代理与反向代理
都是代理,一个正一个反,那么到底是是啥反了?答案是代理的对象反了
通常说三个角色,客户端—代理—服务端
-
正向代理是代理客户端,隐藏客户端的真实身份
如VPN,VPN服务器会代替我们去访问目标网站。对于目标网站而言,交互的对象是代理,他并不知道代理的背后是谁,甚至不知道是不是代理。
目的正是让服务端不知道是谁在访问。
-
反向代理是代理服务端,隐藏服务端的真实身份
如10086或者10010客服热线,我们拨的同一个电话,但是对面的人大概率每次都不一样,但他们都会说自己是客服,实际上我们也不关心是谁,我们只关心能不能解决自己的问题。
很显然客服不止一个,我们拨过去后系统会选择空闲的客服接线给我们,以保证不会出现长时间排队的情况。
虽然代理了服务端,隐藏其身份,但这个不是目的。
目的是让代理去选择合适的服务端,只是没打算告知客户端罢了。对客户端而言,交互对象是代理,并不知道代理的对象是谁
正向代理代理的对象是客户端,反向代理代理的对象是服务端
主要是用在服务器层面的,为了避免偏题,此处不再赘述,有兴趣的可以去这两个地方看看,讲得不错
https://blog.csdn.net/lixiangss1993/article/details/87934562
5.静态代理与动态代理
上述步骤就是静态代理,但存在以下缺陷
代理主题是为真实主题专门配备的,因此是一对一的关系,且使用同一个接口进行规范。
因此代理主题必须基于已有的真实主题,每新增一个真实主题需要新增一个代理主题。
动态代理的出现则是为了解决这种缺陷
动态代理利用反射机制在运行时创建代理类,客户端调用代理来执行服务端方法,因此一个代理类适用于所有服务端。
package com.company.test.invocation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//抽象主题
interface Subject {
void Request(String s);
}
//真实主题
class RealSubject implements Subject {
@Override
public void Request(String s) {
System.out.println("访问真实主题方法...");
}
}
//代理
class ProxyHandler implements InvocationHandler {
private Object object;
public ProxyHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoke " + method.getName());
method.invoke(object, args);
System.out.println("After invoke " + method.getName());
return null;
}
}
public class InvocationTest {
public static void main(String[] args) {
Subject subject = new RealSubject();
Subject realSubject = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyHandler(subject));
realSubject.Request("hello?");
}
}
通过这样的处理方式,我们将subject
托管给ProxyHandler
代理。
我们可以在invoke
方法中进行前置处理,后置处理,甚至修改参数(args),或者直接抛出异常
实际使用的过程中,我们通常使用AOP
(面向切面编程)以注解的方式进行代理,有兴趣的可以看看之前我整理的JWT
的自定义切面拦截器
https://blog.csdn.net/qq_25978501/article/details/108409031
后记
代理模式真要学完可能得几天时间,有空再整理细说吧,先做了解,实战中会遇到不少的,特别是AOP
几乎算是面试必问的了
作者:Echo_Ye
WX:Echo_YeZ
Email :echo_yezi@qq.com
个人站点:在搭了在搭了。。。(右键 - 新建文件夹)