代理 proxy 设计模式

一.概述

       代理模式也是平时比较常用的设计模式之一,代理模式其实就是提供了一个新的对象,实现了对真实对象的操作,或成为真实对象的替身.在日常生活中也是很常见的.例如A要租房,为了省麻烦A会去找中介,中介会替代A去筛选房子,A坐享中介筛选的结果,并且交房租也是交给中介,这就是一个典型的日常生活中代理模式的应用.平时打开网页,最先开到的一般都是文字,而图片等一些大的资源都会延迟加载,这里也是使用了代理模式.

       代理模式的组成:

       Abstract Subject:抽象主题-声明真实主题和代理主题共同的接口

       Real Subject:真实主题-真实的对象,需要被代理主题引用

       Proxy Subject:代理主题-因为ProxySubject引用了RealSubject,并且实现了跟RealSubject一样的接口,所以ProxySubject可以操作RealSubject,还可以提供一些附加操作,例如before & after




       代理模式常用基于场景的分类:

       1.Virtual Proxy:虚拟代理其实就是通过代理的模式对消耗资源比较大的对象做了一个延迟加载,就是什么时候用到这个对象才去创建它.

       2.Remote Proxy:远程代理是比较经典的应用了,类似于C/S模式(主要拦截并控制远程方法的调用,做代理防火墙之类的).

       3.Smart Reference Proxy:智能引用代理可以给引用的对象提供一些额外的操作,例如实现里面中介Searching和Prepare contract的动作.

       4.Access Proxy;保护代理可以控制一个对象的访问,必要时候提供一系列的权限管理.

       5.Copy-on-write Proxy:写时拷贝(克隆)代理其实是Virtual Proxy的分支,提供了拷贝大对象的时候只有在对象真正变化后才会进行拷贝(克隆)的操作(延迟拷贝).


       代理模式的优缺点:

       优点:

       1.代理作为调用着和真实对象的中间层,降低了模块间和系统的耦合性

       2.可以以一个小对象代理一个大对象,达到优化系统提高运行速度的目的

       3.提供RealSubject的权限管理

       4.容易扩展,RealSubject和ProxySubject都接口化了,RealSubject更改业务后只要接口不变,ProxySubject可以不做任何修改.

       缺点:

       1.同优点1,因为调用者和真实对象多了一个中间层,所以会增加调用响应的时间

二.实现

       这里就拿A找中介租房为Demo来构建代理模式.

1.普通代理

       根据场景先定义一个抽象主题,IHouse,提供三个方法,分别是获取房屋信息,签合同和付租金.

[java]  view plain  copy
  1. /** 
  2.  * Created by jesse on 15-7-24. 
  3.  */  
  4. public interface IHouse {  
  5.     void getHouseInfo();  
  6.     void signContract();  
  7.     void payFees();  
  8. }  
       接下来定义真实主题,并实现IHouse接口.增加房屋名称和价格两个属性,填充借口方法,在获取房屋信息的时候就把房屋名称和价格log出来;签合同的时候log出签合同的时间,付租金的时候log出价格.

[java]  view plain  copy
  1. public class House implements IHouse{  
  2.     private final String TAG = House.class.getSimpleName();  
  3.     private String name;  
  4.     private double price;  
  5.   
  6.     public House(String name, double price){  
  7.         this.name = name;  
  8.         this.price = price;  
  9.     }  
  10.   
  11.     @Override  
  12.     public void getHouseInfo() {  
  13.         Log.i(TAG, "House Info- name:" + name + "  ¥:" + price);  
  14.     }  
  15.   
  16.     @Override  
  17.     public void signContract() {  
  18.         Log.i(TAG, "Contract:" + name + "  signed at" +  
  19.                new SimpleDateFormat("HH:mm:ss").format(SystemClock.uptimeMillis()));  
  20.     }  
  21.   
  22.     @Override  
  23.     public void payFees() {  
  24.         Log.i(TAG, "Bill: name-" + name + "  $-" + price);  
  25.     }  
  26. }  
       定义房屋代理,同样需要实现IHouse接口,并持有House的引用.可以看到代理类其实就像有封装House,提供了一些附加操作,例如客户要看房子的时候代理会先检索自己库存的房屋信息,签合同之前要准备合同之类的.

[java]  view plain  copy
  1. public class ProxyHouse implements IHouse{  
  2.     private final String TAG = ProxyHouse.class.getSimpleName();  
  3.     private IHouse house;  
  4.     public ProxyHouse(IHouse house){  
  5.         this.house = house;  
  6.     }  
  7.     @Override  
  8.     public void getHouseInfo() {  
  9.         Log.i(TAG, "searching");  
  10.         house.getHouseInfo();  
  11.         Log.i(TAG, "search finished");  
  12.     }  
  13.   
  14.     @Override  
  15.     public void signContract() {  
  16.         Log.i(TAG, "prepare contract");  
  17.         house.signContract();  
  18.     }  
  19.   
  20.     @Override  
  21.     public void payFees() {  
  22.         house.payFees();  
  23.     }  
  24. }  
       对于客户来说,完全不用跟House进行直接交互,这里先定义一个房子叫唐顿庄园,租金5k,建立一个房屋代理,把唐顿庄园委托给代理.客户要找房子,签合同,付租金直接找代理就行了.

[java]  view plain  copy
  1. IHouse house = new House("Downton Abbey"5000);  
  2. IHouse proxyHouse = new ProxyHouse(house);  
  3. Log.i(TAG, "looking for a perfect house");  
  4. proxyHouse.getHouseInfo();  
  5. Log.i(TAG, "thinking");  
  6. proxyHouse.signContract();  
  7. proxyHouse.payFees();  
  8. Log.i(TAG, "so easy");  

       整个代理模式的流程可以从下面的时序图展示出来.Client只跟代理进行交互.

2.虚拟代理

       虚拟代理前面有介绍,就是基于代理模式又做了延迟加载来节省内存,但是如果某个对象要在多个没有固定时序地方使用的时候就要进行判空,也会一定程度上牺牲性能(有点像代理模式+懒汉模式).这里还是拿租房的例子来展示.

       这里就假设House是一个很庞大的对象,在创建的时候很耗费资源,那我们就更改成当Custom需要用它的时候才去初始化.这里就在ProxyHouse构造的时候先判House的引用是否为空,然后才会初始化House,当然如果这里有多线程并发的话可以根据不同的场景进行加锁或者双检锁来保证线程安全.

[java]  view plain  copy
  1. public ProxyHouse(){  
  2.     if (null == house)  
  3.         house = new House("Downton Abbey"5000);  
  4. }  
[java]  view plain  copy
  1. IHouse proxyHouse = new ProxyHouse();  
  2. Log.i(TAG, "looking for a perfect house");  
  3. proxyHouse.getHouseInfo();  
  4. Log.i(TAG, "thinking");  
  5. proxyHouse.signContract();  
  6. proxyHouse.payFees();  
  7. Log.i(TAG, "so easy");  
3.强制代理

       强制代理是反其道而行之的代理模式,一般情况下代理模式都是通过代理来找到真实的对象,而强制代理则是通过真实对象才能找到代理也就是说由真实对象指定代理,当然最终访问还是通过代理模式访问的.从名字还能看出它跟其他代理的一个不同,就是强制用代理.拿上面普通代理的例子来说,Custom看不到实体的House的时候它只能通过代理来访问,但是由于没有限制,Custom也可以直接绕过ProxyHouse来访问House,但是强制代理就多了一个限制,Custom必须通过ProxyHouse才能访问House.就像一些房东嫌麻烦,有房客直接电话过来说要看房,房东给出一个中介的电话说你跟中介联系吧.

       首先需要在接口里面添加一个获取代理的接口

[java]  view plain  copy
  1. public interface IHouse {  
  2.     void getHouseInfo();  
  3.     void signContract();  
  4.     void payFees();  
  5.     IHouse getProxy();  
  6. }  
       真实对象实现接口,并在getProxy中实例化代理,同时在其他方法里面做代理判断,只有使用自身自定的代理才会正常进行.

[java]  view plain  copy
  1. public class House implements IHouse{  
  2.     private final String TAG = House.class.getSimpleName();  
  3.     private String name;  
  4.     private double price;  
  5.     private IHouse proxy;  
  6.   
  7.     public House(String name, double price){  
  8.         this.name = name;  
  9.         this.price = price;  
  10.     }  
  11.   
  12.     @Override  
  13.     public void getHouseInfo() {  
  14.         if (isProxy())  
  15.             Log.i(TAG, "House Info- name:" + name + "  ¥:" + price);  
  16.         else  
  17.             Log.i(TAG, "Please use correct proxy");  
  18.     }  
  19.   
  20.     @Override  
  21.     public void signContract() {  
  22.         if (isProxy())  
  23.             Log.i(TAG, "Contract:" + name + "  signed at" +  
  24.                     new SimpleDateFormat("HH:mm:ss").format(SystemClock.uptimeMillis()));  
  25.         else  
  26.             Log.i(TAG, "Please use correct proxy");  
  27.   
  28.     }  
  29.   
  30.     @Override  
  31.     public void payFees() {  
  32.         if (isProxy())  
  33.             Log.i(TAG, "Bill: name-" + name + "  $-" + price);  
  34.         else  
  35.             Log.i(TAG, "Please use correct proxy");  
  36.     }  
  37.   
  38.     @Override  
  39.     public IHouse getProxy() {  
  40.         if (null == proxy)  
  41.             proxy = new ProxyHouse(this);  
  42.         return proxy;  
  43.     }  
  44.   
  45.     private boolean isProxy(){  
  46.         if (null == proxy)  
  47.             return false;  
  48.         else  
  49.             return true;  
  50.     }  
  51. }  
       如果这个时候直接操作House对象,或者通过Custom构建的代理来访问都会返回以下结果


       所以我们必须使用由真实对象指定的代理才可以正常得访问.

[java]  view plain  copy
  1. IHouse house = new House("Downton Abbey"5000);  
  2. house = house.getProxy();  
  3. Log.i(TAG, "looking for a perfect house");  
  4. house.getHouseInfo();  
  5. Log.i(TAG, "thinking");  
  6. house.signContract();  
  7. house.payFees();  

       但是这里的强制代理有个Bug,强制代理其实并没有生效,Custom还是可以直接访问House,例如我通过下面的方式来进行访问,只是通过getProxy创建并获取代理,但是我不用代理还是直接用House的实例进行访问,这个时候还是可以正常访问的.后续会想办法解了这个Bug并且更新上来的.

[java]  view plain  copy
  1. IHouse house = new House("Downton Abbey"5000);  
  2. house.getProxy();//这里只是通过getProxy创建出代理  
  3. Log.i(TAG, "looking for a perfect house");  
  4. house.getHouseInfo();  
  5. Log.i(TAG, "thinking");  
  6. house.signContract();  
  7. house.payFees();  

4.动态代理

       上面介绍的都是自己先写好的代理类,这样代理关系都是固定的,当代理多个真实对象的时候就要写多个代理类,并且会产生冗余的代码,扩展性和可维护性都不高,而动态代理是基于反射实现了在程序运行的过程中才决定代理什么对象.像AOP的核心思想就是动态代理.(这里使用的是Java的动态代理)

       既然是动态代理就不需要ProxyHouse也不需要实现IHouse接口了,这里写一个ProxyHandler实现InvocationHandler的invoke接口,并且提供一个根据Proxy构建出来的代理实例给Custom.在通过反射调用真实对象具体的方法之前打印出该方法的名字.

[java]  view plain  copy
  1. public class ProxyHandler implements InvocationHandler{  
  2.     private final String TAG = ProxyHandler.class.getSimpleName();  
  3.     Object targetObj;  
  4.   
  5.     public Object newProxyInstance(Object targetObj){  
  6.         this.targetObj = targetObj;  
  7.         return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(),  
  8.                     targetObj.getClass().getInterfaces(), this);  
  9.     }  
  10.   
  11.     @Override  
  12.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  13.         Object ret;  
  14.         Log.i(TAG, "method name:" + method.getName());  
  15.         ret = method.invoke(targetObj, args);  
  16.         return ret;  
  17.     }  
  18. }  
[java]  view plain  copy
  1. ProxyHandler proxy = new ProxyHandler();  
  2. IHouse house = (IHouse) proxy.newProxyInstance(new House("Downton Abbey"5000));  
  3. Log.i(TAG, "looking for a perfect house");  
  4. house.getHouseInfo();  
  5. Log.i(TAG, "thinking");  
  6. house.signContract();  
  7. house.payFees();  
  8. Log.i(TAG, "so easy");  
       从结果可以看出在真正invoke真实对象的方法之前都会打印出方法名,也可以在这里做一些其他的对象控制.


       这个时候整个过程的时序图就变成下面的样子了,通过JDK的Proxy对象和反射的机制来支撑起来动态代理的核心功能.


三.总结

       代理模式的使用场景还是挺多的,可以降低对象的复杂度,对项目进行解耦(特别是动态代理的AOP)等,学习设计模式其实最适合的方法就是拿来用,在适用于该模式的场景下灵活得去运用它才算是真正的掌握一种模式.



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值