故事
周末放假,小孙睡到12点才告别周公醒来,顿时饥肠辘辘。舍长小王正准备去食堂买饭,作为一个好舍长小王主动要帮小孙带饭。小孙点了米饭、宫保鸡丁、芬达。小孙起床洗漱,然后静待舍长。小孙心理寻思道舍长果然是好舍长啊。下面我们先把这个故事抽象一下,画作类图。这个类图即代理模式。
代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问。怎么理解这句话呢?从生活的角度来说就是:用一个对象A代替另一个对象B来处理本来应该由对象B完成的事情,例如上面买饭的例子,又如通过黄牛买票等等。从代码的角度来说则是:用一个对象A来过滤、预处理对对象B的调用 。不管从哪个角度看吧,代理都需要拥有和实际对象的需要被代理的全部方法。
静态代理
类图如上所示了,定义也看了。接下来我们按照类图来看看代码。
接口(宿舍成员)
/** * 宿舍成员接口 * 开发时间:2014-8-12 下午9:17:19 */ public interface DormitoryMember { //买主食 public boolean buyStapleFood(String stapleFoodName); //买菜 public boolean buyDish(String dishName); //买饮料 public boolean buyDrink(String drinkName); }
代理(宿舍长)
//代理类 public class DormitoryMemberProxy implements DormitoryMember { //持有一个被代理的对象 private DormitoryMember dormitoryMember; //构造时传入被代理对象 public DormitoryMemberProxy(DormitoryMember dormitoryMember){ this.dormitoryMember=dormitoryMember; } //代理买主食 public boolean buyStapleFood(String stapleFoodName) { return dormitoryMember.buyStapleFood(stapleFoodName); } //代理买菜 public boolean buyDish(String dishName) { return dormitoryMember.buyDish(dishName); } //代理买饮料 public boolean buyDrink(String drinkName) { return dormitoryMember.buyDrink(drinkName); } }
实际类(小孙)
//实际的类 public class DormitoryMemberImpl implements DormitoryMember { //买主食 public boolean buyStapleFood(String stapleFoodName) { try{ System.out.println("买到主食" + stapleFoodName); }catch(Exception e){ return false; } return true; } //买菜 public boolean buyDish(String dishName) { try{ System.out.println("买到菜:" + dishName); }catch(Exception e){ return false; } return true; } //买饮料 public boolean buyDrink(String drinkName) { try{ System.out.println("买到饮料:" + drinkName); }catch(Exception e){ return false; } return true; } }
故事继续
宿舍长去到食堂买饭,结果小孙要的芬达断货了。于是宿舍长久提着米饭和菜就回去了,小孙吃着米饭和菜没有饮料难以下咽。但是,也不好说宿舍长,舍长是好舍长啊。将就的吃了午饭。时间来的晚饭时间,小孙在英雄联盟里正杀的起劲,那又闲工夫去买饭于是这工作有落到了好舍长身上。这次小孙学聪明了交代舍长说,如果有他点的东西没有了就打个电话回来在决定买什么替代。
问题来了
这里需求已经改变了,如果小孙要的东西没有了的话,要给小孙打电话以决定是不是买其他的或者不买。这个电话自然是宿舍长来打。所以我们要对代理类进行修改!这也就是使用代理模式的好处,我们关闭了对实际类的修改。代理类修改后的代码如下:
//代理类 public class DormitoryMemberProxy implements DormitoryMember { //持有一个被代理的对象 private DormitoryMember dormitoryMember; //构造时传入被代理对象 public DormitoryMemberProxy(DormitoryMember dormitoryMember){ this.dormitoryMember=dormitoryMember; } //代理买主食 public boolean buyStapleFood(String stapleFoodName) { boolean buySuccess=dormitoryMember.buyStapleFood(stapleFoodName); if( buySuccess=false){ System.out.println("给小孙打电话"); return false; }else{ return true; } } //代理买菜 public boolean buyDish(String dishName) { boolean buySuccess=dormitoryMember.buyDish(dishName); if( buySuccess=false){ System.out.println("给小孙打电话"); return false; }else{ return true; } } //代理买饮料 public boolean buyDrink(String drinkName) { boolean buySuccess=dormitoryMember.buyDrink(drinkName);; if( buySuccess=false){ System.out.println("给小孙打电话"); return false; }else{ return true; } } }
问题(1)
看代码可以知道,我们要对代理类中的每一个方法都进行同样的修改。极限假设我们实现的这个接口有1000个方法,哭死都不冤枉。
问题(2)
极限假设,伟大的宿舍长不止帮小孙买饭,还要帮小李买衣服,还要帮小赵买电脑。换句话说宿舍长要代理不同的接口。但是这是不可能实现的,因为在代理类所持有的实际类是确定的,也就是说每一个代理类只能代理一个借口。
显然很不科学,这么多的代码重复出现。有不科学的情况出现,自然就会有办法因对。最怕的是没有发现问题,没有问题就没有改进。下面我们上动态代理。
动态代理
首先看问题一,既然当代理调用实际类时在代理类中的预处理都是一样的那么我们要达到代码不重复的话就只要保证调用实际类之前或者之后都回掉到某个处理就好了。当然以前好像没有学过可以对很多种不同的方法都同样回掉一个函数,因为类的参数个数和类型以及返回值都是不一致的。因此,java中就为此专门有一个接口可以实现这样的功能,InvocationHandler接口。
再看问题二,对于问题二我们必须将代理类和被代理类解耦,通常我们解决这类问题就是用反射。这也不例外,这里java提供的反射机制可以在运行时产生代理类,即动态代理。这篇博客已经太长了,分开来吧。先把动态代理的代码贴上。其中实际类和接口没有变化!
代理类工厂
import java.lang.reflect.Proxy; //代理工厂类 public class ProxyFactory { //实际类 private Object target; public ProxyFactory(Object target){ this.target=target; } //根据传入的实际类生成代理 public Object getInstance(){ //实例化一个继承了InvocationHandler接口的预处理对象 ProxyHandler handler=new ProxyHandler(target); //反射得到代理类 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);//handler即集中处理在动态代理类对象上的方法调用 } }
继承了InvocationHandler接口的handler类
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class ProxyHandler implements InvocationHandler { private Object target; public ProxyHandler(Object target){ this.target=target; } //所有代理方法的公共处理方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result =null; result=method.invoke(target, args); //要买的东西断货时 if(result.toString().equals("false")){ System.out.println("给小孙打电话"); return result; }else{ return result; } } } public class Client { public static void main(String[] args){ //真正得到的代理类在客户端强转 DormitoryMember dormitoryMember=(DormitoryMember)new ProxyFactory(new DormitoryMemberImpl()).getInstance(); dormitoryMember.buyStapleFood("米饭"); } }
总结:动态代理通过反射把代理类和实际类之间的耦合解开了,同时通过继承了InvocationHandler接口的handler类对代理的方法进行统一的处理。也就解决静态代理所遇到的问题。
代理模式深入(一)——静态到动态