《大话设计模式》第三篇之代理模式
前言
首先是例行的国际惯例,本文写于本人学习设计模式的路上,适合同样学习设计模式的朋友交流使用,大神误入请留下您宝贵的意见,先行谢过;
其次,上一篇策略模式中使用了动态代理的方式解决了问题,但是当时对于代理模式不甚了解,经过这两天努力学习代理模式,终于有所感悟,特此写下本文以作交流学习。
定义
百度百科
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
个人拙见
代理模式其实就是在你不能直接引用一个类,或者是需要在原有类的基础上多构造一些行为或者说取代原有类的某些行为时可以创建一个代理类,原来的类作为代理类的一个属性,对于我们不关心的方法,直接交给原来的类去执行,对于我们关心的方法,可以在代理类中进行改造,此时客户端通过调用代理类就可以间接的操作原有的类。
类图
分类
代理模式分为两种情况,分别是静态代理和动态代理,两者在本质上是没有什么区别的,只是代理类创建的时间不同,首先静态代理的代理类是由程序猿创建的,在编译时将其源代码编译成class文件,在调用时直接由类加载器加载已经编译好的class文件即可,但是动态代理是在程序调用过程中通过反射的机制动态生成的代理类的class文件,然后再由类加载器加载动态生成的class文件,下面分别举例说明静态代理和动态代理。
静态代理
说静态代理的时候我顺便给大家说说我十一假期干嘛去了,但愿不会被打死(你们这是嫉妒我),十一的时候呢跟着我女朋友去她家了,从北京坐了十个小时的高铁到了广州,然后又辗转半天才到女朋友家,一开门我就把准备了好几天的话一口气对着她妈说完了,只见我这未来岳母一脸懵逼的看着我,然后来了句"!@#$%^&&*%",卧槽,这下轮到我懵逼了,此时只听我女朋友淡淡的来了一句"我妈不会说普通话,只会说粤语",MDZZ,你早说啊,害我提前准备了两天的说辞全白费了,然后在那呆了四五天,基本的套路就是我跟她爸妈的交流都是她爸妈先问,然后她翻译成普通话,然后我回答,她再添油加醋的翻译成粤语,好蛋疼。 针对以上情况,这不刚好是代理模式吗?而且还是静态代理,有人问为啥是静态代理,原因就是你没对象(NEW的不算),难不成你还能有好几个对象不成?,下面看看使用代码怎么实现上述的情景:
首先要有一个交流接口,规定我和GF的爸妈交流什么(肯定不是从诗词歌赋谈到。。。)
public interface Communication { public void talkHome();//谈家庭 public void talkWork();//谈工作 public void talkMarry();//谈结婚 public void talkOther();//谈其他 }
其次,我作为委托人(类),要实现上面的接口:
public class Me implements Communication{ @Override public void talkHome() { System.out.println("我说我的家庭"); } @Override public void talkWork() { System.out.println("我说我的工作"); } @Override public void talkMarry() { System.out.println("我说结婚"); } @Override public void talkOther() { System.out.println("我说其他"); } }
然后我的GF作为代理人(类),要持有我的对象,同时实现上面的交流接口:
public class GirlFriend implements Communication{ private Me me; public GirlFriend(){ super(); this.me = new Me(); } @Override public void talkHome() { me.talkHome(); System.out.println("GF翻译加工我的家庭"); } @Override public void talkWork() { me.talkWork(); System.out.println("GF翻译加工我的工作"); } @Override public void talkMarry() { me.talkMarry(); System.out.println("GF翻译加工关于结婚"); } @Override public void talkOther() { me.talkOther(); System.out.println("GF翻译加工关于其他"); } }
接下来就是开始交流了(客户端调用):
public class Test { public static void main(String[] args) { Communication com = new GirlFriend(); System.out.println("岳父母问:你的家庭怎么样啊?"); com.talkHome(); System.out.println("岳父母问:你的工作怎么样啊?"); com.talkWork(); System.out.println("岳父母问:你们打算啥时候结婚啊?"); com.talkMarry(); System.out.println("岳父母问:其他话题?"); com.talkOther(); } }
交流结果如下:
有一点要说明,上面代码情景只有GF代理Me,没有代理未来的岳父母,毕竟这个GF太累了还是让她歇会儿吧。
总结
通过上述的例子可以看到,实现静态代理的方式十分简单,只需要一个接口,代理类和委托类实现这个接口,客户端调用时,只需要知道代理类而不需要认识委托类,这样就成功的将委托类和客户端进行了解耦合,这是静态代理的一个优点;
但是静态代理也有其缺点,首先,代理类和委托类实现了同样的接口,那么在接口中添加或者删除一个方法要同时影响到两个类,增加了维护代码的复杂度,其次,代理对象只服务于一种类型的对象,此时如果要服务于多个类型的对象,那么必然要给每一种类型的对象添加一个代理类,此时就会出现很多的代理类,并且如果各个类型的对象中含有相同的方法的话,那么就会出现大量的重复代码,对于代码的维护以及扩展都不是十分友好,但是如果需要代理的类的数量很少,那么使用静态代理会是一个很好的选择;
应用
静态代理的一种应用是什么呢?假设现在有一个类,你只能拿到它的引用,但是你不能修改源代码,但是你又需要去修改其中的一项功能,这时候就可以使用到静态代理的方式,比如说数据库连接的Connection,习惯性使用完以后调用它的close方法关闭连接,但是数据库的关闭和开启是十分耗费资源的,我们常用的就是使用连接池来处理这种情况,此时我们可以创建一个代理类,实现Connection接口,其他的方法还调用Connection原来的方法,只修改close方法为归还连接到连接池,具体代码请参考开发小组左潇龙代理模式。
动态代理
基于上述静态代理的缺点,那么有没有一种方式,在有多个类需要代理时不创建等量的代理类呢,答案是可以的,使用动态代理即可,此处主要是JDK自带的动态代理,也可以使用cglib等方式实现,cglib的实现方式回头专门写个文章来介绍吧,这里还是专注代理模式来说吧,上一篇策略模式的文章中用到了动态代理,这里我就不再专门举例子,还是把上一篇中用到的例子解释清楚吧:
回忆
先简单回忆一下上一个使用动态代理的例子,首先有两种类型的策略,一种是国家规定的年假天数,一种是公司规定的年假天数,对应类型的策略类使用注解的方式区别,同时注解上还标明了对应的工作年限,下面我一种类型的策略贴一个策略类,其他的策略类一样:
@NationalRange(@ValidateRange(min=1,max=10)) public class FiveDays implements VacationDays{ @Override public int getVacationDays() { System.out.println("国家规定:工作1到10年,年假5天"); return 5; } } @CompanyRange(@ValidateRange(min=2,max=5)) public class CompanyThreeDays implements VacationDays{ @Override public int getVacationDays() { System.out.println("在当前公司2年到5年,3天年假"); return 3; } }
然后在简单工厂中通过动态代理返回策略类:
public VacationDays create(int workYears,int nowCompanyYears){ List<Class<? extends VacationDays>> list = new ArrayList<Class<? extends VacationDays>>(); for(Class<? extends VacationDays> cla : daysList){ Annotation range = handleAnnotation(cla);//获取到策略类上的注解 if(range instanceof NationalRange){//判断是否是国家规定的年假策略 NationalRange nrange = (NationalRange)range; if(workYears >= nrange.value().min() && workYears < nrange.value().max()){ list.add(cla); } }else if(range instanceof CompanyRange){//判断是否是公司规定的年假策略 CompanyRange crange = (CompanyRange)range; if(nowCompanyYears >= crange.value().min() && nowCompanyYears < crange.value().max()){ list.add(cla); } } } //动态代理 return DaysProxy.getProxy(list); }
然后是创建的代理类:
public class DaysProxy implements InvocationHandler{ private List<Class<? extends VacationDays>> list; public DaysProxy(List<Class<? extends VacationDays>> list){ super(); this.list = list; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { int result = 0; if(method.getName().equals("getVacationDays")){ for(Class<? extends VacationDays> cla : list){ result += (int)method.invoke(cla.newInstance(), args); } System.out.println("年假总天数"+result); return result; } return null; } public static VacationDays getProxy(List<Class<? extends VacationDays>> list){ return (VacationDays) Proxy.newProxyInstance(DaysProxy.class.getClassLoader(), new Class<?>[]{VacationDays.class}, new DaysProxy(list)); } }
解释
动态代理是在运行期通过反射的方式生成代理类,并且生成代理类的字节码文件保存到本地,生成的代理类是java.lang.reflect.Proxy和你传入的接口的子类,因此在调用java.lang.reflect.Proxy的newProxyInstance方法生成代理类时,可以将生成的代理类转换成你调用上述方法时传入的任意接口,如上述代码片段中的getProxy(List