学习设计模式之禅——代理模式

来源:互联网 发布:手机pdf阅读器 知乎 编辑:程序博客网 时间:2024/05/22 06:41

先看一个例子,是有关于打游戏杀怪兽的。

//先定义一个游戏者接口public interface IGamePlayer {//登陆游戏public void login(String user, String password);//杀怪,网络游戏的主要角色public void killBoss();//升级public void upgrade();}//游戏者public class GamePlayer implements IGamePlayer {private String name = "";//通过构造方法传递名称public GamePlayer(String _name) {this.name = _name;}//打怪,最期望的就是打怪@Overridepublic void killBoss() {System.out.println(this.name + "在打怪!");}//在游戏之前肯定要登录吧@Overridepublic void login(String user, String password) {System.out.println("登陆名" + user + "的用户" + this.name + "登陆成功");}//升级,升级有很多方法,花钱买是一种,做任务又是一种@Overridepublic void upgrade() {System.out.println(this.name + "又升了一级");}}//场景类public class Client {public static void main(String[] args) {//定义一个痴迷的玩家IGamePlayer player = new GamePlayer("张三");//开始打游戏,记录下时间System.out.println("开始时间是:2013-10-12 21:51");player.login("zhangsan", "password");//开始杀怪player.killBoss();//升级player.upgrade();//记录结束游戏时间System.out.println("结束时间是:2013-10-13 21:51");}}

但是有时候我们很累,要熬夜,但是我们又想玩游戏,又想摆脱那些烦恼,如何解决呢?有办法,现在游戏公司代练的公司很多,我把自己账号交给代练人员,由他们帮我升级,去打怪,非常好的想法,我们修改一下类结构.

//增加一个GamePlayerProxy来代表游戏代练者,它也不能有作弊的方法呀,游戏代练者也是手动打怪呀,因此需要同样继承IGamePlayer接口,代码如下:public class GamePlayerProxy implements IGamePlayer {private IGamePlayer gamePlayer = null;//通过构造方法传递要对谁进行代练public GamePlayerProxy(IGamePlayer _gamePlayer) {this.gamePlayer = _gamePlayer;}//代练杀怪@Overridepublic void killBoss() {this.gamePlayer.killBoss();}//代练登录@Overridepublic void login(String user, String password) {this.gameplayer.login(user, password);}//升级@Overridepublic void upgrade() {this.gamePlayer.upgrade();}}

很简单,首先通过构造方法说明要代谁打怪升级,然后通过手动开始代用户打怪\升级.场景类Client代码也稍作改动.

public class Client {public static void main(String[] args) {//定义一个痴迷的玩家IGamePlayer player = new GamePlayer("张三");//然后再定义一个代练者IGamePlayer proxy = new GamePlayerProxy(player);//开始打游戏,记录下时间System.out.println("开始时间是:2013-10-12 21:51");proxy.login("zhangsan", "password");//开始杀怪proxy.killBoss();//升级proxy.upgrade();//记录结束游戏时间System.out.println("结束时间是:2013-10-13 21:51");}}

代理模式(Proxy Pattern)是一个使用率非常高的模式,其定义:Provide a surrogate or placeholder for another object to control access to it(为其他对象提供一种代理以控制对这个对对象的访问。)代理模式也叫做委托模式,它是一项基本的设计技巧,很多其他的模式本质上是在更特殊的场合采用了委托模式。


代理模式通用代码如下

//抽象主题类,可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。public interface Subject {//定义一个方法public void request();}//具体主题角色类,也叫被委托的角色、被代理的角色,它才是最重要的,是业务逻辑的具体执行者。public class RealSubject implements Subject {@Overridepublic void request() {//业务逻辑处理}}//代理类public class Proxy implements Subject {//要代理哪个实现类private Subject subject = null;//默认被代理者public Proxy() {this.subject = new Proxy();}//通过构造方法传递代理者public Proxy(Object...objects) {}//实现接口中的方法@Overrdiepublic void request() {this.before();this.subject.request();this.after();}//预处理private void before() {//do something}//善后处理private void after() {//do something}}

代理模式的优点
1)职责清晰:这是的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
2)高扩展性:具体的主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以再不做任何修改的情况下使用。
3)这在我们以上的讲解中都没有体现出来,不过在我们一下的动态代理章节中你就会看到代理店智能化有兴趣的读者也可以看看Structs是如何把表单元素映射到对象上的。

代理模式的使用场景
我相信第一次接触代理模式的读者肯定很郁闷,为什么要用代理呀?想想我们现实世界吧,打官司为什么要找个律师?因为你不想参与中间过程的是是非非,只要完成自己的啊答辩就成,其他的比如事前调查、事后追查都由律师来搞掂,这就是为了减轻你的负担。代理模式的使用场景非常多,大家可以看看Spring AOP,这是一个非常典型的动态代理。

代理模式的扩展

普通代理


在网络上代理服务器设置分为透明代理和普通代理,是什么意思呢?透明代理就是用户不用设置服务地址,就可以直接访问,也就是说代理服务器对用户来说是透明的,不用知道它的存在的;普通代理则是需求用户自己设置代理服务器的IP地址,用户必须知道代理的存在.我们设计模式中的普通代理模式和强制代理模式也是类似的一种结构,普通的代理就是我们需要知道的代理存在,也就是累世的GamePlayerProxy这个类的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不关心代理是否存在,其代理的产生游真实的角色决定的,这样的解释比较复杂,我们还是用实例来讲解.


首先说普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色,这是比较简单的.我们以上面的例子作为扩展,我自己作为一个游戏玩家,我肯定不练级了,也就是场景类不能再直接new一个GamePlayer对象了,它必须由GamePlayerProxy来进行模拟场景,修改如下

//普通代理的游戏者public class GamePlayer implements IGamePlayer {private String name = "";//通过构造方法限制谁能创建对象,并同时传递姓名(在构造方法中,传递一个IGamePlayer对象,检查谁能创建真实的角色,当然还可以有其他的限制,比如类名必须是Proxy类等等,我们可以根据实际情况进行扩展)public GamePlayer(IGamePlayer _gamePlayer, String _name) {if(_gamePlayer == null) {throw new Exception("不能创建真实角色");} else {this.name = _name;}}//打怪,最期望的就是打怪@Overridepublic void killBoss() {System.out.println(this.name + "在打怪!");}//在游戏之前肯定要登录吧@Overridepublic void login(String user, String password) {System.out.println("登陆名" + user + "的用户" + this.name + "登陆成功");}//升级,升级有很多方法,花钱买是一种,做任务又是一种@Overridepublic void upgrade() {System.out.println(this.name + "又升了一级");}}//普通代理的代理者,仅仅修改了构造方法,传递一个代理者的名称,即可代理,在这种情况下,系统更加简洁了,调用者只知道代理存在就可以了,不需要知道代理了谁.public class GamePlayerProxy implements IGamePlayer {private IGamePlayer gamePlayer = null;//通过构造方法传递要对谁进行代练public GamePlayerProxy(String name) {try {gamePlayer = new GamePlayer(this, name);} catch (Exception e) {}}//代练杀怪@Overridepublic void killBoss() {this.gamePlayer.killBoss();}//代练登录@Overridepublic void login(String user, String password) {this.gameplayer.login(user, password);}//升级@Overridepublic void upgrade() {this.gamePlayer.upgrade();}}//普通代理的场景类public class Client {public static void main(String[] args) {//然后再定义一个代练者IGamePlayer proxy = new GamePlayerProxy("张三");//开始打游戏,记录下时间System.out.println("开始时间是:2013-10-12 21:51");proxy.login("zhangsan", "password");//开始杀怪proxy.killBoss();//升级proxy.upgrade();//记录结束游戏时间System.out.println("结束时间是:2013-10-13 21:51");}}

运行结果完全相同.在该模式下,调用者只知道代理而不用知道真实角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,改模式非常适合对扩展性要求比较高的场合.当然,在实际的项目中,一般都是通过约定来禁止new一个真实的角色的,这也是一个非常好的方案.

注意:普通代理模式的约束问题,尽量通过团队内的编程规范类的约束,因为每个主题类是可以被重复用的和可维护的,使用技术约束的方式对系统是一种非常不利的因素.

强制代理

强制代理在设计模式中比较另类,为什么这样说呢?一般的思维都是通过代理找到真实的角色的,但是强制代理却是要"强制",你必须通过真实角色查找到代理角色,否则你不能访问.甭管你是通过代理类还是通过直接new一个主题角色类,都不能访问,只有通过真实角色指定的代理类才可以访问,也就是说由真实角色管理代理角色.这么说吧,高层模块new了一个真实角色的对象,返回的确实代理角色,这好比你和一个明星比较熟,相互认识,有件事情你需要想她确认一下,于是你就直接拨通了明星的电话:
"喂,沙比呀,我要见一下XXX导演,你帮下忙!"
"不行呀衰哥,我这几天很忙呀,你找我的经纪人吧......"
郁闷了吧,你是想直接绕过她的代理,谁知道返回的还是她的代理,这就是强制代理,你可以不用知道代理存在,但是你的所作所为还是需要代理为你提供.我们修改一下IGamePlayer接口,增加一个getProxy的方法.

//修改后的IGamePlayer//先定义一个游戏者接口public interface IGamePlayer {//登陆游戏public void login(String user, String password);//杀怪,网络游戏的主要角色public void killBoss();//升级public void upgrade();//每个人都可以找一个自己的代理public IGamePlayer getProxy();}//强制代理的真实角色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;}//打怪,最期望的就是打怪@Overridepublic void killBoss() {if (this.isProxy()) {System.out.println(this.name + "在打怪!");} else {System.out.println("请使用制定的代理访问");}}//在游戏之前肯定要登录吧@Overridepublic void login(String user, String password) {if (this.isProxy()) {System.out.println("登陆名" + user + "的用户" + this.name + "登陆成功");} else {System.out.println("请使用制定的代理访问");}}//升级,升级有很多方法,花钱买是一种,做任务又是一种@Overridepublic void upgrade() {if (this.isProxy()) {System.out.println(this.name + "又升了一级");} else {System.out.println("请使用制定的代理访问");}}//检验是否是代理访问private boolean isProxy() {if (this.proxy == null) {return false;} else {return true;}}}

强制代理的代理类

public class GamePlayerProxy implements IGamePlayer {private IGamePlayer gamePlayer = null;//通过构造方法传递要对谁进行代练public GamePlayerProxy(IGamePlayer _gamePlayer) {this.gamePlayer = _gamePlayer;}//代练杀怪@Overridepublic void killBoss() {this.gamePlayer.killBoss();}//代练登录@Overridepublic void login(String user, String password) {this.gameplayer.login(user, password);}//升级@Overridepublic void upgrade() {this.gamePlayer.upgrade();}//代理的代理暂时没有,就是我自己@Overridepublic IGamePlayer getProxy() {return this;}}

下面看看几个场景

//直接访问真实角色public class Client {public static void main(String[] args) {//然后再定义一个游戏角色IGamePlayer player = new GamePlayer("张三");//开始打游戏,记录下时间System.out.println("开始时间是:2013-10-12 21:51");player.login("zhangsan", "password");//开始杀怪player.killBoss();//升级player.upgrade();//记录结束游戏时间System.out.println("结束时间是:2013-10-13 21:51");}}

想看看能运行吗?运行结果如下所示:
开始时间是:2013-10-12 21:51
请使用制定的代理访问
请使用制定的代理访问
请使用制定的代理访问
结束时间是:2013-10-13 21:51

它要求你必须通过代理来访问,你想要直接访问它,门儿都没有,好,你要我通过代理来访问,我就生产一个代理,如下场景

//直接访问代理类public class Client {public static void main(String[] args) {//然后再定义一个游戏角色IGamePlayer player = new GamePlayer("张三");//然后在定义一个代练者IGamePlayer proxy = new GamePlayerProxy(player);//开始打游戏,记录下时间System.out.println("开始时间是:2013-10-12 21:51");proxy.login("zhangsan", "password");//开始杀怪proxy.killBoss();//升级proxy.upgrade();//记录结束游戏时间System.out.println("结束时间是:2013-10-13 21:51");}}

这次能访问吗,还是不行,运行结果如下所示:
开始时间是:2013-10-12 21:51
请使用制定的代理访问
请使用制定的代理访问
请使用制定的代理访问
结束时间是:2013-10-13 21:51

还是不能访问,为什么呢?它不是真实角色制定的对象,这个代理对象是你自己new出来的,当然真实对象不忍了,这个好比就是那个明星,人家已经告诉你去找她的代理人了,你随便找个代理人能成吗?你必须去找她指定的代理才成,我们修改一下场景类

//强制代理的场景类public class Client {public static void main(String[] args) {//然后再定义一个游戏角色IGamePlayer player = new GamePlayer("张三");//获取制定的代理IGamePlayer proxy = plyer.getProxy();//开始打游戏,记录下时间System.out.println("开始时间是:2013-10-12 21:51");proxy.login("zhangsan", "password");//开始杀怪proxy.killBoss();//升级proxy.upgrade();//记录结束游戏时间System.out.println("结束时间是:2013-10-13 21:51");}}

运行结果如下:
开始时间是:2013-10-12 21:51
登陆名......
张三在......
张三 又升一级....
结束时间是:2013-10-13 21:51

OK,可以正常访问代理了.强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色.高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要一产生一个代理出来,代理的管理已经由真实的角色自己完成.

代理是有个性的
一个类可以实现多个接口,完成不同的任务的整合.也就是说代理类不仅仅可以实现主体接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上做增强,这种增强的本质通常就是通过对目标对象的方法进行拦截和过滤.例如游戏代理是需要收费的,升一级需要5元钱,这个计算功能就是代理类的个性,它应该在代理的接口中定义.增加一个IProxy的接口,其作用就是计算代理的费用.

//代理类的接口public interface IProxy {//计算费用public void count();}//代理类public class GamePlayerProxy implements IGamePlayer,IProxy {private IGamePlayer gamePlayer = null;//通过构造方法传递要对谁进行代练public GamePlayerProxy(IGamePlayer _gamePlayer) {this.gamePlayer = _gamePlayer;}//代练杀怪@Overridepublic void killBoss() {this.gamePlayer.killBoss();}//代练登录@Overridepublic void login(String user, String password) {this.gameplayer.login(user, password);}//升级@Overridepublic void upgrade() {this.gamePlayer.upgrade();this.count();}//计算费用@Overridepublic void count() {System.out.println("升级费用是:150元")}}


虚拟代理
虚拟代理(Virtual Proxy)
听着很负责,其实很简单,我们只要吧代理模式的通用代码稍微修改一下就成了虚拟代理,修改后的代理类如下:

//虚拟代理类public class Proxy implements Subject {//要代理哪个实现类private Subject subject;//实现接口定义方法@Overridepublic void request() {//判断一下真实主题是否初始化if (subject == null) {subj = new RealSubject();}subject.request();}}

在需要的时候才初始化主题对象,可以避免被代理对下较多而引起的初始化缓慢的问题.其缺点是需要在每个方法中判断主题对喜爱那个是否被创建,这就是虚拟代理,非常简单.

动态代理

放在最后将的一般都是压轴大戏,动态代理就是如此,上面的章节都是一个引子,动态代理才是重头戏.什么是动态代理?动态代理是在实现阶段不用关心代理谁,而是在运行阶段才指定代理哪一个对象.相对来说,自己写代理类的方式就是静态代理.现在有一个非常流行的名称叫做面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制,我们看看动态代理是如何实现的,还是以打游戏为例.
我们增加了一个InvocationHandler接口和GamePlayIH类,作用就是产生一个代理对象,其中InvocationHandler是JDK提供的动态代理接口,对被代理的方法进行代理,我们来看程序,接口保持不变,实现类也没有变化,先看动态代理类

//动态代理类public class GamePlayIH implements InvocationHandler {//被代理者Class cls = null;//被代理的实例Object obj = null;//我要代理谁public GamePlayIH(Object _obj) {this.obj = _obj;}//调用被代理的方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = method.invoke(this.obj, args);return result;}}

其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用.我们来详解一下InvocationHandler接口,动态代理是根据被代理的接口生产所有的方法,也就是说给定一个接口,动态代理会宣称"我已经实现该接口下所有的方法了",

//动态代理的场景类public class Client {public static void main(String[] args) {//定义一个痴迷的玩家IGamePlayer player = new GamePlayer("张三");//定义一个handlerInvocationHandler handler = new GamePlayIH(player);//开始打游戏,记录下时间System.out.println("开始时间是:2013-10-12 21:51");//获得类的class loaderClassLoader loader = player.getCLass().getClassLoader();//动态产生一个代理者IGamePlayer proxy = (IGamePlayer)Proxy.newProxyInstance(loader, new Class[]{IGamePlayer.class}, handler);proxy.login("zhangsan", "password");//开始杀怪proxy.killBoss();//升级proxy.upgrade();//记录结束游戏时间System.out.println("结束时间是:2013-10-13 21:51");}}

很奇怪是吗?不要着急,继续看下去.其运行结果如下:


开始时间是:2013-10-12 21:51
登陆名......
张三在......
张三 又升一级....
结束时间是:2013-10-13 21:51

我们还是让代练者帮我们打游戏,但是我们既没有创建代理类也没有实现IGamePlayer接口,这就是动态代理.别急,动态代理可不仅仅就这么多内容.还有更重要的,如果想让游戏登陆后发一个信息给我们,防止账号被人盗用嘛,该怎么处理呢?直接修改代理类GamePlayer?这不是一个好办法,好办法如下:

//修正后的动态代理类public class GamePlayIH implements InvocationHandler {//被代理者Class cls = null;//被代理的实例Object obj = null;//我要代理谁public GamePlayIH(Object _obj) {this.obj = _obj;}//调用被代理的方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = method.invoke(this.obj, args);if (method.getName().equalsIgnoreCase("login")) {System.out.println("有人在用我的账号");}return result;}}

太棒了!有人用我的账号就发送一个信息,然后看看自己的账号是不是被人盗了,非常好的方法,这就是AOP编程,AOP编程没有使用什么心的技术,但是它对我们的设计,编码有非常大的影响,对于日志,事务,权限等都可以在系统设计极端不用考虑,而在设计后通过AOP的方式切过去.既然动态代理是如此地诱人,我们来看看通用动态代理模型

//抽象主题类,可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。public interface Subject {//定义一个方法public void doSomething();}//具体主题角色类,也叫被委托的角色、被代理的角色,它才是最重要的,是业务逻辑的具体执行者。public class RealSubject implements Subject {@Overridepublic void doSomething() {//业务逻辑处理}}//动态代理的Handlerpublic class MyInvocationHandler implements InvocationHandler {//被代理的实例private Object obj = null;//我要代理谁public MyInvocationHandler(Object _obj) {this.obj = _obj;}//调用被代理的方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.invoke(this.obj, args);}}//动态代理类public class DynamicProxy<T> {public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) {//寻找JoinPoint连接点,AOP框架使用元数据定义if (true) {//一个判断条件//执行一个前置通知(new BeforeAdvice()).exec();}//执行目标,并返回结果return (T)Proxy.newProxyInstance(loader, interfaces, handler);}}//通知接口及实现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();}}

我们要主要到程序是怎么实现的,我们有这样的语句Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterface(), handler);该语句是重新生成一个对象,为什么要重新生成?我们要用代理呀,注意subject.getClass().getInterface(),意思是说查找到该类的所有接口,然后实现接口所有的方法.当然了,方法都是空的,是由谁负责接管呢?是由我们的 new MyInvocationHandler(subject),也就是handler这个对象.于是我们知道一个类的动态代理类是这样的一个类,由InvocationHandler的实现类实现所有的方法,由其invoke方法接管所有的方法的实现,其动态调用过程是这样的:
doSomething()(Client中)-->invoke()(DynamicProxy中)-->"invoke()"(MyInvocationHandler中)-->Subject对象


其实我们上面的代码还有更进一步的扩展余地,注意看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 super.newProxyInstance(loader, classes, handler);}}

场景类
动态代理的场景类

public class Client {public static void main(String[] args) {//定义一个主题Subject subject = new RealSubject();Subject proxy = SubjectDynamicProxy.newProxyInstance(subject);//代理行为proxy.doSomething();}}

是不是更加简单了?可能读者就问了,这样与静态代理还有什么区别?都是需要实现一个代理类,有区别,注意看父类,动态代理的主要意图就是解决我们常说的"审计"问题,也就是横切面编程,在不改变我们现有的代码结构的情况下增强或控制对象的行为.

注意
要实现动态代理的首要条件是:被代理类必须实现一个接口,回想一下前面的分析吧,当然了,现在也有很多技术如CGLIB可以实现不需要接口也可以实现动态代理的方法.

再次说明,以上的动态代理只是一个通用的框架,如果你想设计自己的AOP框架,完全可以在此基础上扩展,我们设计的是一个通用代理,只要有一个接口,一个实现类,就而已使用该代理,完成代理的所有功效.

原创粉丝点击