【专题系列】搞定设计模式之—— 十二 :代理模式
点击上方“java大数据修炼之道”,选择“设为星标”
优质文章, 第一时间送达
来源 | https://www.cnblogs.com/three-fighter/p/12650023.html 作者:三分恶
目录
什么是代理模式?
代理模式扩展
动态代理实例
动态代理详解
普通代理
强制代理
有个性的代理
动态代理
代理模式优缺点
什么是代理模式?
代理模式(Proxy Pattern)是一个使用率非常高的模式,其定义如下:
Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供 一种代理以控制对这个对象的访问。) |
代理模式是一种对象结构型模式。在代理模式中引入了一个新的代理对象,代理对象在客户 端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增添客户需 要的额外的新服务。
代理模式的通用类图如图12-1所示:
图10-1:代理模式通用类图
根据类图,代理模式包含三个角色:
● Subject:抽象主题角色,它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
● RealSubject:具体主题角色也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。
● Proxy:代理主题角色,也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制 委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
来看看具体的代码实现。
Subject:抽象主题类
抽象主题类声明了真实主题类和代理类的公共方法,它可以是接口、抽象类或具体类。
public interface Subject {//定义了一个方法public void request();
}
RealSubject:真实主题类
真实主题类继承了抽象主题类,提供了业务方法的具体实现
public class RealSubject implements Subject{//实现方法@Overridepublic void request() {//具体逻辑}}
Proxy:代理类
代理类也是抽象主题类的子类,它对真实主题对象引用,调用在真实主题中实现的业务方法,在调用时可以在原有业务方法的基础上附加一些新的方法来对功能进行扩充或约束
public class Proxy implements Subject{//要代理的类 private Subject subject=null;//通过构造方法传递被代理的类的实例public Proxy(Subject subject) {this.subject=subject;}//实现接口Subject中的方法@Overridepublic void request() {this.before();//调用真实Subject中的方法this.subject.request();this.after();}//预处理private void before() {}//善后处理private void after() {}}
在实际开发过程中,代理类的实现比上述代码要复杂很多,代理模式根据其目的和实现方式不同可分为很多种。
代理模式扩展
普通代理
在网络上代理服务器设置分为透明代理和普通代理:
透明代理就是用户 不用设置代理服务器地址,就可以直接访问,也就是说代理服务器对用户来说是透明的,不 用知道它存在的;
普通代理则是需要用户自己设置代理服务器的IP地址,用户必须知道代理 的存在。
设计模式中的普通代理和强制代理也是类似的一种结构,普通代理就是我们要知道代理的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定。
以代练代打游戏举例说明,普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色,这是比较简单的。以代练打游戏升级的例子作为扩展,游戏玩家,自己不练级 ,场景类不直接new一个GamePlayer对象了,由GamePlayerProxy来进行模拟场景。
图12-2:普通代理类图
具体代码如下:
IGamePlayer:接口
public interface IGamePlayer {//登录游戏public void login(String user,String password);//杀怪,网络游戏的主要特色public void killBoss();//升级public void upgrade();
}
GamePlayer:游戏者
在构造函数中,传递进来一个IGamePlayer对象,检查谁能创建真实的角色
public class GamePlayer implements IGamePlayer{private String name = "";// 构造函数限制对象创建,并同时传递姓名public GamePlayer(IGamePlayer _gamePlayer, String _name) throws Exception {if (_gamePlayer == null) {throw new Exception("不能创建真实角色!");} else {this.name = _name;}}@Overridepublic void login(String user, String password) {System.out.println("登录名为"+user + "的用户" + this.name + "登录成功!");}@Overridepublic void killBoss() {System.out.println(this.name + "在打怪!");}@Overridepublic void upgrade() {System.out.println(this.name + " 又升了一级!");}}
GamePlayerProxy:代理者
仅仅修改了构造函数,传递进来一个代理者名称,即可进行代理,在这种改造下,系统 更加简洁了,调用者只知道代理存在就可以,不用知道代理了谁。
public class GamePlayerProxy implements IGamePlayer{private IGamePlayer gamePlayer = null;// 通过构造函数传递要对谁进行代练public GamePlayerProxy(String name){try {gamePlayer = new GamePlayer(this,name);} catch (Exception e) {// TODO 异常处理 } }}}//代练登录@Overridepublic void login(String user, String password) {this.gamePlayer.login(user, password);}//代练杀boss@Overridepublic void killBoss() {this.gamePlayer.killBoss();}//代练升级@Overridepublic void upgrade() {this.gamePlayer.upgrade();}}
Client:场景类
public class Client {/*** @param args*/public static void main(String[] args) {//然后再定义一个代练者IGamePlayer proxy = new GamePlayerProxy("铁锤");//开始打游戏,记下时间戳System.out.println("开始时间是:2020-04-10 22:10");proxy.login("zhangSan", "password");//开始杀怪proxy.killBoss();//升级proxy.upgrade();//记录结束游戏时间System.out.println("结束时间是:2020-04-10 22:12");}}
在代理中,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合。当然,在实际的项目中,一般都是通过约定来禁止new一个真实的角色,这也是一个 非常好的方案。
强制代理
强制代理在设计模式中比较另类,一般的思维都是通过代理找到真实 的角色,但是强制代理却是要“强制”,必须通过真实角色查找到代理角色,否则你不能访 问。不管是通过代理类还是通过直接new一个主题角色类,都不能访问,只有通过真实角色指定的代理类才可以访问,也就是说由真实角色管理代理角色。
图12-2:强制代理类图
代码如下:
IGamePlayer:接口
在接口上增加了一个getProxy方法,真实角色GamePlayer可以指定一个自己的代理,除 了代理外谁都不能访问。
public interface IGamePlayer {//登录游戏public void login(String user,String password);//杀怪,网络游戏的主要特色public void killBoss();//升级public void upgrade();//每个人都可以找一下自己的代理public IGamePlayer getProxy();
}
GamePlayer:强制代理的真实角色
增加了一个私有方法,检查是否是自己指定的代理,是指定的代理则允许访问,否则不允许访问。
public class GamePlayer implements IGamePlayer{private String name = "";//我的代理是谁private IGamePlayer proxy = null;public GamePlayer(String _name) {this.name = _name;}//找到自己的代理@Overridepublic IGamePlayer getProxy() {this.proxy = new GamePlayerProxy(this);return this.proxy;}// 构造函数限制对象创建,并同时传递姓名public GamePlayer(IGamePlayer _gamePlayer, String _name) throws Exception {if (_gamePlayer == null) {throw new Exception("不能创建真实角色!");} else {this.name = _name;}}@Overridepublic void login(String user, String password) {System.out.println("登录名为"+user + "的用户" + this.name + "登录成功!");}@Overridepublic void killBoss() {System.out.println(this.name + "在打怪!");}@Overridepublic void upgrade() {System.out.println(this.name + " 又升了一级!");}}
GamePlayerProxy:强制代理的代理类
public class GamePlayerProxy implements IGamePlayer{private IGamePlayer gamePlayer = null;//构造函数传递用户名public GamePlayerProxy(GamePlayer _gamePlayer) {this.gamePlayer = _gamePlayer;}@Overridepublic void login(String user, String password) {gamePlayer.login(user, password);}@Overridepublic void killBoss() {gamePlayer.killBoss();}@Overridepublic void upgrade() {gamePlayer.upgrade();}//代理类没有代理类,返回自己@Overridepublic IGamePlayer getProxy() {return this;}}
Client:场景类
public class Client {public static void main(String[] args) {// 定义一个游戏的玩家IGamePlayer gamePlayer = new GamePlayer("拳拳");// 获取该玩家的代理IGamePlayer proxy = gamePlayer.getProxy();// 开始打游戏,记下时间戳System.out.println("开始时间是:2020-04-10 22:10");proxy.login("quanquan", "password");// 开始杀怪proxy.killBoss();// 升级proxy.upgrade();// 记录结束游戏时间System.out.println("结束时间是:2020-04-10 22:12");}}
强制代理的概念就是要从真实角色查找到代理角色,不允 许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本 就不需要产生一个代理出来,代理的管理已经由真实角色自己完成。
有个性的代理
一个类可以实现多个接口,完成不同任务的整合。也就是说代理类不仅仅可以实现主题 接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。例如游戏代理是需要收费的,升一级需要5元钱,这个计算功能就是代理类的个性,它应该在代理的接口中定义, 如图12-3所示:
图12-3:代理类的个性
增加了一个IProxy接口,其作用是计算代理的费用。
public interface IProxy {//计算费用public void count();
}
GamePlayerProxy类实现该接口:
public class GamePlayerProxy implements IGamePlayer,IProxy {private IGamePlayer gamePlayer = null;//通过构造函数传递要对谁进行代练public GamePlayerProxy(IGamePlayer _gamePlayer){this.gamePlayer = _gamePlayer;}//代练杀怪public void killBoss() {this.gamePlayer.killBoss();}//代练登录public void login(String user, String password) {this.gamePlayer.login(user, password);}//代练升级public void upgrade() {this.gamePlayer.upgrade();this.count();}//计算费用public void count(){System.out.println("升级总费用是:150元");}}
同时在upgrade方法中调用该方法,完成费用结算。
动态代理
什么是动态代理?动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。
相对来说,自己写代理类的方式就是静态代理。面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制。
动态代理实例
还是以打游戏为例,类图修改一下以实现动态代理,如图12-4所示:
图12-4:动态代理
在类图中增加了一个InvocationHandler接口和GamePlayIH类,作用就是产生一个对象的代理对象,其中InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理。我们来看程序,接口保持不变,实现类也没有变化。
GamePlayIH:动态代理类
其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。
InvocationHandler接口——动态代理是根据被代理的接口生成所有的方法, 也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”,通过 InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。
public class GamePlayIH implements InvocationHandler{//被代理者Class cls=null;//被代理的实例Object object=null;//我要代理谁public GamePlayIH(Object _obj) {this.object=_obj;}// 调用被代理的方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = method.invoke(this.object, args);// 如果是登录方法,则发送信息if (method.getName().equalsIgnoreCase("login")) {System.out.println("有人在用我的账号登录!");}return result;}}
*Client: 场景类
public class Client {public static void main(String[] args) throws Throwable{//定义一个玩家IGamePlayer gamePlayer= new GamePlayer("辣个男人");//定义一个handlerInvocationHandler handle=new GamePlayIH(gamePlayer);//开始打游戏,记下时间戳System.out.println("开始时间是:2020-04-10 10:45");//获得类的class loaderClassLoader loader=gamePlayer.getClass().getClassLoader();//动态产生一个代理者IGamePlayer proxy=(IGamePlayer) Proxy.newProxyInstance(loader, new Class[] {IGamePlayer.class}, handle);//登录proxy.login("lagenanren", "password");//开始杀怪proxy.killBoss();//升级proxy.upgrade();//记录结束游戏时间System.out.println("结束时间是:2020-04-10 11:45");}}
运行结果:
动态代理详解
在上面的动态代理类里,游戏登录时会通知。
这就是AOP编程。AOP编程没有使用什么新的技术,但是它对设计、编码有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通 过AOP的方式切过去。
通用动态代理模型,类图如图 12-5所示:
图12-5:动态代理通用类图
动态代理实现代理的职责,业务逻辑Subject实现相关的 逻辑功能,两者之间没有必然的相互耦合的关系。通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务。
具体代码实现如下:
Subject:抽象主题
public interface Subject {//业务操作public void doSomething(String str);
}
RealSubject:真实主题
public class RealSubject implements Subject{@Overridepublic void doSomething(String str) {System.out.println("do something!---->" + str);}}
MyInvocationHandler:动态代理的Handler类
所有通过动态代理实现的方法全部通过invoke方法调
public class MyInvocationHandler implements InvocationHandler{//被代理的对象private Object target = null;// 通过构造函数传递一个对象public MyInvocationHandler(Object _obj) {this.target = _obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//执行被代理的方法return method.invoke(this.target, args);}}
DynamicProxy:动态代理类
public class DynamicProxy<T> {public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {// 寻找JoinPoint连接点,AOP框架使用元数据定义if (true) {// 执行一个前置通知(new BeforeAdvice()).exec();}// 执行目标,并返回结果return (T) Proxy.newProxyInstance(loader, interfaces, h);}}
通知接口及实现
public interface IAdvice {//通知只有一个方法,执行即可public void exec();
}public class BeforeAdvice implements IAdvice{@Overridepublic void exec() {System.out.println("我是前置通知,我被执行了!");}}
动态代理的场景类:
public class Client {public static void main(String[] args) {//定义一个主题Subject subject = new RealSubject();//定义一个HandlerInvocationHandler handler = new MyInvocationHandler(subject);//定义主题的代理Subject proxy = DynamicProxy.newProxyInstance(subject.getClass(). getClassLoader(), subject.getClass().getInterfaces(),handler);//代理的行为proxy.doSomething("完事了");}}
运行结果:
看看程序是怎么实现的。在DynamicProxy类 中,有这样的方法:
this.obj=Proxy.newProxyInstance(c.getClassLoader(),c.getInterfaces(),new MyInvocationHandler(_obj));
该方法是重新生成了一个对象。注意 c.getInterfaces(),查找到该类的所有接口,然后实现接口的所有方法。当然了,方法都是空的,由谁具体负责接管呢?是new MyInvocationHandler(_Obj)这个对象。于是就知道一个类的动态代理类是这样的一个类, 由InvocationHandler的实现类实现所有的方法,由其invoke方法接管所有方法的实现,其动态调用过程如图12-6所示。
图12-6: 动态代理调用过程示意图
以上的代码还有更进一步的扩展余地,DynamicProxy类,它 是一个通用类,不具有业务意义,可以再产生一个实现类:
public class SubjectDynamicProxy extends DynamicProxy {public static <T> T newProxyInstance(Subject subject) {// 获得ClassLoaderClassLoader loader = subject.getClass().getClassLoader();// 获得接口数组Class<?>[] classes = subject.getClass().getInterfaces();// 获得handlerInvocationHandler handler = new MyInvocationHandler(subject);return newProxyInstance(loader, classes, handler);}
}
如此扩展以后,高层模块对代理的访问会更加简单:
public class Client1 {public static void main(String[] args) {//定义一个主题Subject subject = new RealSubject();//定义主题的代理Subject proxy = SubjectDynamicProxy.newProxyInstance(subject);//代理的行为proxy.doSomething("了了");}}
代理模式优缺点
代理模式优点
能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
代理模式缺点
由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求 的处理速度变慢,例如保护代理。
实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。
参考:
【1】:《设计模式之禅》
【2】:《design pattern java》
【3】:《研磨设计模式》
推荐学习路线:
1、java学习路线全攻略(资料、视频、源码、项目实战)
2、【史上最强实战项目合集】java项目20套 +完整版java学习视频
推荐面试技巧:
1、面试时被质疑学历、年龄该怎么办?
2、如何应对面试官问你职业规划问题
3、面试的时候问:你的期望薪资多少?怎么谈?
4、面试官问,你有什么问题需要问我的吗?
5、给培训出来的Java程序员的一点建议,教你如何找工作
最近热文推荐:
1、看完这篇Redis缓存三大问题,保你能和面试官互扯。
2、Java中:如何把异常设计的更优雅?
3、面试:SpringBoot中的条件注解底层是如何实现的?
4、单怼多线程,60道面试题,你能答上几个?(附答案)
5、这21 个刁钻的HashMap 面试题,我把阿里面试官吊打了!
6、用 float 存储金额,老板说损失从工资里扣!
7、别用Date了,Java8新特性之日期处理,现在学会也不迟!
8、【收藏了】10分钟读懂进程线程、同步异步、阻塞非阻塞、并发并行
注:加群要求 微信学习交流群:
1、想学习JAVA这一门技术, 对JAVA感兴趣零基础,想从事JAVA工作的。
2、工作1-5年,感觉自己技术不行,想提升的
3、如果没有工作经验,但基础非常扎实,想提升自己技术的。
4、还有就是想一起交流学习的。
小编个人微信
(如果你有学习上不懂的问题、需要学习视频资源等;都可长按识别上方二维码添加小编为好友, 我将免费为你提供完整的学习路线和各种视频学习资源)
如果您觉得不错,请别忘了转发、分享、点赞让更多的人去学习, 您的举手之劳,就是对小编最好的支持,非常感谢!
如何您想进技术群交流,关注公众号在后台回复 “加群”,或者 “学习” 即可
著作权归作者所有,欢迎大家投稿 (投稿作者我会在公众号一一署名一并感谢
要加群的赶紧上车
,请加我微信2782278837统一拉群
—写文不易,你的转发就是对我最大的支持—
看完本文有收获?请转发分享给更多有需要的人
关注 java大数据修炼之道
每天学习java技术,你想学的Java知识这里都有!
微信扫描二维码,关注我的公众号
写留言
喜欢就给个“在看”
【专题系列】搞定设计模式之—— 十二 :代理模式相关推荐
- C#设计模式之十二代理模式(Proxy Pattern)【结构型】
一.引言 今天我们要讲[结构型]设计模式的第七个模式,也是"结构型"设计模式中的最后一个模式,该模式是[代理模式],英文名称是:Proxy Pattern.还是老套路,先从名字上来 ...
- 设计模式(十)——代理模式
为什么80%的码农都做不了架构师?>>> 一.定义 为其他对象提供一种代理以控制对该对象的访问 二.要素 1.抽象角色:通过接口或抽象类声明真实角色实现的业务方法 2.代理角色 ...
- Java进阶篇设计模式之十二 ---- 备忘录模式和状态模式
前言 在上一篇中我们学习了行为型模式的策略模式(Strategy Pattern)和模板模式(Template Pattern).本篇则来学习下行为型模式的两个模式,备忘录模式(Memento Pat ...
- Java设计模式(十二) 策略模式
策略模式介绍 策略模式定义 策略模式(Strategy Pattern),将各种算法封装到具体的类中,作为一个抽象策略类的子类,使得它们可以互换.客户端可以自行决定使用哪种算法. 策略模式类图 策略模 ...
- (原创)无废话C#设计模式之十二:Bridge
无废话C#设计模式之十二:Bridge 意图 将抽象部分与实现部分分离,使它们都可以独立的变化. 场景 还是说我们要做的网络游戏,多个场景需要扩充的问题我们已经采用了创建型模式来解决.现在的问题就是, ...
- 二十三种设计模式(第十二种)-----代理模式(Proxy)
二十三种设计模式(第十二种)-----代理模式(Proxy) 尚硅谷视频连接https://www.bilibili.com/video/BV1G4411c7N4?from=search&se ...
- 一个系列搞定校招——简历篇
上一篇一个系列搞定校招--综合篇总体介绍了校招从简历到面试的各个环节,没看过的可以先看上一篇,接下来将分别从每一个环节详细介绍,本篇先说[简历篇]. 前面说过,简历是求职的敲门砖,一份好的简历必然会给 ...
- 设计模式(十二)—— 享元模式
设计模式(十二)-- 享元模式 定义 结构 案例实现 优缺点和使用场景 JDK源码解析 定义 运用共享技术来有效地支持大量细粒度对象的复用.它通过共享已经存在的对象来大幅度减少需要创建的对象数量.避免 ...
- 云计算设计模式(十二)——索引表模式
云计算设计模式(十二)--索引表模式 创建索引过的被查询条件经常被引用的数据存储等领域.这种模式可以通过允许应用程序更快速地定位数据来从数据存储中检索提高查询性能. 背景和问题 许多数据存储通过使用主 ...
最新文章
- mysql5.5 二进制安装
- 查看Android API文档的正确方式
- linux centos7 设置开机 进入命令行 不进入图形界面
- c mysql ssh_c ssh mysql数据库
- 您如何从Python的stdin中读取信息?
- java中的集合_你真的了解Java中的集合类么?
- webapi 状态返回 php,web api不想建实体,用dynamic类型返回数据
- UVA 10558 A Brief Gerrymander
- python中ht_python – 如何在Google App Engine上正确安装ht...
- 如何避免_如何避免钢板弹簧受损
- ghost版32位win10系统,win10系统下载地址
- 3Dmax玻璃材质参数应该怎样设置
- MacBook连接打印机-惠普HP LaserJet Pro MFP M427fdn 连接方法
- vue前端导出(XLSX)
- 程序员一般可以从什么平台接私活?
- 使用 Groovy 合并 MSN 聊天记录
- 计算机操作系统重装,手把手教你电脑怎样重装系统
- 小猿圈分享利用python网络爬虫获取网易云歌词
- JavaScript写一个虚拟软键盘,可拼音输入
- 正交变换法中的A矩阵怎么求