设计模式-代理模式

一、代理模式的核心思想

代理模式就是给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。就是一个人或者一个机构代替另一个人或者另一个机构去采取一些行动。代理模式中的代理者就好比中介机构,它提供了对被代理对象的一切事物。

代理模式与适配器模式和装饰器模式相似,它们之间的区别是:

  • 适配器模式是将一个类A转换成另一个类B。
  • 装饰器模式是为一个类A增加新的功能,从而变成类B。
  • 代理模式是为一个类A转换操作类B。

它们三者的限制条件层层递进,递进关系如下图所示。

代理模式中的"代理"要想实现代理任务,就必须与被代理的"对象"使用共同的接口。所以自然而然你会想到在Java中使用一个抽象类或者接口(推荐)来实现这个共同的接口。于是代理模式就有3个角色组成。

  • 被代理对象的接口Sourcable:声明了代理对象和代理者的共同接口。
  • 被代理对象 Source:定义真实的对象。
  • 代理者 Proxy:内部包含对代理对象的引用,并且提供与代理对象角色相同的接口。

使用类图来表示下三者间的关系如下图所示。

下面来看具体的实现。

(1) Sourcable类的源代码如下程序所示,其定义了一个接口函数 operation()。

源接口 Sourcable.iava

java 复制代码
package structure.proxy
/**
* @author Minggg
* 源接口
*/
public interface Sourcable {
	public void operation();
}

(2) Source.java是 Sourcable.java 的一个实现,其函数 operation()负责往控制台输出一个字符串原始类的方法。其源代码如下程序所示。

源类 Source.java

java 复制代码
package structure.proxy;
/**
* @author Minggg
* 源类
*/
public class Source implements Sourcable {
	public void operation(){
		System.out.println("原始类的方法");
	}
}

(3) 代理类Proxy.java采用了典型的对象适配器模式,它首先拥有一个Sourcable 对象source,注意,不同的是该对象在构造函数中进行初始化,不能够从外部传入。然后它实现了Sourcable.iava接口,以期保持与 source 同样的接口,并在重写的 operation()函数中调用 source 的 operation()函数,在调用前后可以调用自己的函数。其源代码如下程序所示。

代理类Proxy.java

java 复制代码
package structure.proxy;
/**
* @author Minggg
* 代理类
*/
public class Proxy implements Sourcable {

	private Source source;
	
	/**
	*创建代理对象
	*/
	public Proxy(){
		super();
		this.source = new Source();
	}
	
	/**
	*调用代理对象的方法
	*/
	public void operation() {
		before();
		source.operation();
		after();
	}
	
	public void before(){
		System.out.printIn("代理前");
	}
	
	public void after(){
		System.out.println("代理后");
	}

这时,我们就可以直接通过 Proxy来操作Source类。如下程序所示,首先需要创建一个代理对象proxy,然后直接调用该对象的operation()方法,即可实现对source的调用。

测试类 Client.java

java 复制代码
package structure.proxy;

/**
* @author Minggg
*/
public class Client {
	public static void main(String[] args){
	
		//创建代理对象
		Sourcable proxy = new Proxy():
		// 调用代理对象的方法
		proxy.operation();
	}
}

运行该程序的结果如下:

java 复制代码
代理前
原始类的方法
代理后

从程序的输出可以看出,通过 Proxy不仅实现了对 Source的调用,还实现了自身的功能,这与装饰器模式很相似,不同的是它们的关注点不同:装饰器模式关注于扩展功能,而代理模式关注于如何调用

二、何时使用代理模式

在对已有的方法进行使用的时候需要对原有方法进行改进或者修改,这时候有两种改进选择:修改原有方法来适应现在的使用方式,或者使用一个"第三者"方法来调用原有的方法,并且对方法产生的结果进行一定的控制。第一种方法明显违背了"对扩展开放、对修改关闭"的开闭原则,而且在原有方法中进行修改可能使得原来类的功能变得模糊和多元化,而使用第二种方式可以将功能划分得更加清晰,有助于后面的维护。所以在一定程度上第二种方式是一个比较好的选择!这就是代理模式。

如果按照使用目的来划分,代理有以下几种。

  • 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以在本机器中,也可以在另一台机器中。远程代理又叫做大使(Ambassador)。
  • 虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。
  • Copy-on-Write 代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时才真正采取行动。
  • 保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
  • Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  • 防火墙(Firewall)代理:保护目标,不让恶意用户接近。
  • 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
  • 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。

在所有种类的代理模式中,虚拟(Virtual)代理、远程(Remote)代理、智能引用代理(SmartReference Proxy)和保护(ProtectorAccess)代理是最为常见的代理模式。

三、Java 中的应用--Java 动态代理机制

Java引入了名为动态代理类(DynamicProxy Class)的新特性,利用它可为已知接口的实现动态地创建代理类。所谓动态代理,即通过代理类:Proxy的代理,接口和实现类之间可以不直接发生联系,而可以在运行期(Runtime)实现动态关联。

Java动态代理类位于 Java.lang.reflect包下,一般主要涉及到以下两个类:

  • 接口InvocationHandler:该接口中仅定义了一个方法:Obiect invoke(Obiect obj, Method method, Object[] args);。在实际使用时,第一个参数obi一般是指代理类,method 是被代理的方法,args为该方法的参数数组。
  • Proxy:该类即为动态代理类,作用类实现了InvocationHandler接口的代理类,其中主要包含以下函数。
    • protected Proxy(InvocationHandler h):构造函数,用于给内部的h赋值。
    • static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
    • static Obiect newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当做被代理类使用。

所谓 Dynamic Proxy是这样一种类:它是在运行时生成的class,在生成它时你必须提供一组interface 给它,然后该class就宣称它实现了这些interface。你当然可以把该 class 的实例当做这些interface 中的任何一个来用。当然,这个 Dynamic Proxy其实就是一个 Proxy,它不会替你做实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。

下面的代码演示了用动态代理来创建一个代理类 DynamicProxy。我们创建的这个DynamicProxy类不需要实现 Sourcable 接口,而是实现了iava.lang,reflect.JnvocationHandler,只提供了一个inyokeC方法。代理对象上的任何方法调用都要通过这一方法进行。观察invokeO的主体,它包含了被调用方法的反射参数 Method,我们可以使用该参数确定当前执行方法的属性。

然而,我们得到的仍然只是一个具有invoke()方法的 InvocationHandler,而不是我们真正想要的Sourcable 对象。动态代理真正的魅力要到创建实际的 Sourcable 实例时才能反映出来。它通过调用DynamicProxy的构造方法初始化了被包装的对象proxyed。完整的代码如下程序所示。

动态代理类DynamicProxy.java

java 复制代码
package structure.proxy;
import java.lang.reflect.InvocationHandlel;
import java.lang.reflect.Method;

public class DynamicProxy implements InvocationHandler {
	private Object proxyed;
	//被代理对象
	public DynamicProxy(Object obj) {
		this.proxyed = obj;
	}
	//代理调用
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	
		Obiect result;
		
		//方法调用之前
		System.out.println("start invoke!");
		
		// 调用原始对象的方法
		result = method.invoke(this.proxyed, args);
		
		//方法调用之后
		System,out.println("end invoke!");
		return result;
	}
}

然后我们就可以像以下程序 一样进行代理调用。

测试类DynamicProxyClient.java

java 复制代码
package structure.proxy;

import java.lang.reflect,InvocationHandler;

public class DynamicProxyClient {
	public static void main(String[] args){
		// 创建原始类对象
		Sourcable source = new Source();
		//创建代理实例
		InvocationHandler handler = new DynamicProxy(source);
		// 取得代理对象
		Sourcable proxy = (Sourcable) java.ang.reflect.Proxy.newProxyInstance(source.getClass()
				.getClassLoader(), source.getClass().getInterfaces(), handler);
		proxy.operation();
	}

}

这段代码实现了对目标对象 source 的代理调用:

  • 根据被代理对象source 创建一个代理类 handler,此处是DynamicProxy对象。
  • 创建动态代理对象 proxy,它的第一个对象为source 类的加载器,第二个对象为该类的接口,第三个对象为代理对象 handler。
  • 通过动态代理对象proxy调用operation()方法,此时会在原始对象Source.operation ()方法前后输出两句字符串。

运行这段代码的输出是:

java 复制代码
start invoke!
原始类的方法
end invoke!

上面的代码表面上看很复杂,但它的作用其实很简单,就是告诉 DynamicProxy 类用一个指定的类加载器来动态创建一个对象,该对象要实现指定的接口(本例为Soucable),并用提供的InvocationHandler来代替传统的方法主体。结果对象在一个imnstanceof Soucable测试中返回true,并提供在实现了Soucable接口的任何类中都能找到的方法。

有趣的是,在 DynamicProxy类的invoke0方法中,完全不存在对Soucable接口的引用。在本例中,我们以构造函数参数的形式,为DynamicProxy提供了Soucable 的一个实例。代理 Soucable 实例上的任何方法调用最终都由DynamicProxy委托给这个"包装的"Soucable。但是,虽然这是最常见的设计,但你必须了解,IinvocationHandler 不一定非要委托给被代理的接口的另一个实例。事实上InvocationHandler 完全能自行提供方法主体,而无须一个委托目标。

最后要注意,如果Soucable接口中发生改变,那么DynamicProxy中的invoke()方法将仍然可移植。例如,假定operation()方法被重命名,那么新的方法名依然会被拦截。

相关推荐
晨米酱12 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
数据智能老司机17 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机18 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机18 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机18 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
使一颗心免于哀伤18 小时前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
数据智能老司机2 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
烛阴2 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript
李广坤2 天前
工厂模式
设计模式