设计模式之代理模式(Proxy Pattern)_补充篇

写在前面:

代理模式的内部原理,作用及远程代理的实现在上一篇博文中都做了详细解释,本文只是对其内容的补充,介绍其它代理

一.虚拟代理

首先,明确虚拟代理的作用:在巨大对象被真正创建出来之前,用虚拟代理来代替巨大对象(保证了巨大对象在需要的时候才被创建,避免资源浪费)。虚拟代理我们可能经常在用,只是不认识它而已,比如:

我们的程序中可能存在密集的计算或者从服务器取得大量数据这样耗时耗资源的操作,一般都会先显示默认信息,或者显示动画表示我们的程序还在努力工作,在操作完成之后显示获得的信息。其中就用到了虚拟代理的思想,只是虚拟代理把等待操作完成期间的工作全部分离出来封装到了一个虚拟代理对象里面,与客户直接交互的是虚拟代理而不是实际做事情的对象

虚拟代理接到客户请求后做了这样几件事情:

  1. 如果实际对象不存在(尚未创建或者尚未创建完成)则记录客户请求,显示默认信息,等到实际对象创建完成之后把请求传递给实际对象
  2. 如果实际对象已经存在,则直接把客户请求委托给实际对象
  3. 在实际对象没有传回操作结果之前,代理对象负责显示默认信息(或者做默认处理)
  4. 得到操作结果后直接传递给客户(没错,代理的任务就是“交”与“接”)

既然是代理,那么就要伪装成实际对象,不能让客户发觉代理的存在,所以虚拟代理的类图是这样的:

Client持有一个虚拟代理对象Proxy,Proxy持有一个具体服务对象MyService

而更重要的是:虚拟代理Proxy与具体服务MyService都扩展自Service接口,所以代理与实际对象具有相同的行为,客户不会发觉Proxy的存在

二.保护代理

保护代理扮演着经纪人的角色(不是所有人都可以联系我背后的明星,你们只能联系我,在我确定你不是坏人之后,才给你转接。。)

换句话说,保护代理为实际对象提供了访问控制,保证了只有合法的调用者才能得到服务

实现的话,类图和上面的虚拟代理完全一样,只是Proxy多了一项判断调用者权限的职责(发现什么了吗?我们好像可以把多个代理结合起来使用,当然这样的代理不再满足类的单一责任原则,不过没关系,我们完全可以创建一个代理的代理来管理一系列代理。。)

其实Java的API提供了对代理模式的支持(动态代理)。Java提供的动态代理的动态体现在Proxy代理类(没错,是类,而不是对象)是在运行时才被创建的,我们不能直接修改Proxy的内部实现,但我们可以通过InvocationHandler来告诉Proxy应该怎么做

采用动态代理来实现保护代理的基本思路是:创建多个代理,不同的代理表示不同权限的调用者对应的服务

那么让我们来设计一个情景,尝试用动态代理实现保护代理:博文点赞机制:可以赞别人的博文,不能赞自己的博文,但博主和游客都可以看到被赞数目

情景比较简单,不过足够说明问题了,下面开始模拟实现。首先要有BlogUser:

package ProxyPattern.ProtectionProxy;

/**
 * 定义博客用户接口
 * @author ayqy
 */
public interface BlogUser {
	public void support();
	public int getSupportCount();
}

还要有ConcreteUser,其实现过程比较简单,不在此展开。下面开始创建动态代理,这里有两种权限,博主与游客,所以我们至少需要两个不同的动态代理

AuthorHandler:

package ProxyPattern.ProtectionProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 实现为作者服务的InvocationHandler
 * @author ayqy
 */
public class AuthorHandler implements InvocationHandler{
	BlogUser user;
	
	public AuthorHandler(BlogUser user){
		this.user = user;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws IllegalAccessException {//声明非法访问异常
			try {
				/*如果博主想要查看被赞数目,则委托给调用者user实现具体操作*/
				if(method.getName().equals("getSupportCount")){
					return method.invoke(user, args);
				}
				/*如果博主要给自己点赞,则抛出异常表示拒绝*/
				if(method.getName().equals("support")){
					throw new IllegalAccessException();
				}
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			return null;//如果调用其它方法,直接无视
	}

}

与此类似,还需要实现VisitorHandler。有了多个代理,我们还需要实现代理的代理,也就是保护代理(视调用者权限返回一个对应的动态代理)

ProtectionProxy:

package ProxyPattern.ProtectionProxy;

import java.lang.reflect.Proxy;

/**
 * 实现保护代理
 * @author ayqy
 */
public class ProtectionProxy {
	/**
	 * @param user 调用者
	 * @return 作者对应的代理
	 */
	public BlogUser getAuthorProxy(BlogUser user){
		return (BlogUser)Proxy.newProxyInstance(user.getClass().getClassLoader()
				, user.getClass().getInterfaces()
				, new AuthorHandler(user));
	}
	
	/**
	 * @param user 调用者
	 * @return 游客对应的代理
	 */
	public BlogUser getVisitorProxy(BlogUser user){
		return (BlogUser)Proxy.newProxyInstance(user.getClass().getClassLoader()
				, user.getClass().getInterfaces()
				, new VisitorHandler(user));
	}
	
	/*在此处插入生成其它代理的方法*/
}

有了这些就足够了,做一个简单的测试:

package ProxyPattern.ProtectionProxy;

/**
 * 实现测试类
 * @author ayqy
 */
public class Test {
	public static void main(String[] args){
		//创建保护代理
		ProtectionProxy proxy = new ProtectionProxy();
		
		System.out.println("博主部分:");
		//创建博主对应的代理
		BlogUser authorProxy = proxy.getAuthorProxy(new ConcreteUser());
		System.out.println("博主要查看被赞总数");
		System.out.println("被赞 " + authorProxy.getSupportCount() + " 次");
		//博主要给自己点赞
		System.out.println("博主要给自己点赞");
		try{
		authorProxy.support();
		}catch(Exception e){
			System.out.println("点赞失败,无法给自己点赞");
		}
		System.out.println("自赞之后,被赞 " + authorProxy.getSupportCount() + " 次");

		
		System.out.println("\n游客部分:");
		//创建游客对应的代理
		BlogUser vistorProxy = proxy.getVisitorProxy(new ConcreteUser());
		System.out.println("游客要查看被赞总数");
		System.out.println("被赞 " + vistorProxy.getSupportCount() + " 次");
		//博主要给自己点赞
		System.out.println("游客要给博主点赞");
		try{
		vistorProxy.support();
		}catch(Exception e){
			System.out.println("点赞失败,原因未知");
		}
		System.out.println("点赞之后,被赞 " + vistorProxy.getSupportCount() + " 次");
	}
}

测试结果:

三.其它代理

代理模式应用很广泛,无法一一列举所有代理,在此简单介绍几种常见的代理:

代理名称 作用
防火墙代理 控制网络资源的访问,保护主题免于“坏客户”的侵害
智能引用代理 当主题被引用时,进行额外的动作,比如计算一个对象被引用的次数
缓存代理 为开销大的运算结果提供暂时存储,也允许多个客户共享结果,以减少计算或网络延迟
同步代理 在多线程的情况下为主题提供安全的访问
复杂隐藏代理(外观代理) 用来隐藏一个类的复杂集合的复杂度,并进行访问控制(比外观模式功能更强大)
写入时复制代理 用来控制对象的复制,方法是延迟对象的复制,直到真正需要复制为止(与虚拟代理类似)

当然,只要理解了最原始的远程代理,遇到这些代理就都很容易理解。。