在上一篇文章“(Dynamic Proxy)动态代理模式的Java实现(http://haolloyin.blog.51cto.com/1177454/333257)”中,大概讲明了 Java 对于动态代理的支持,以及使用 Java 自带的动态代理机制的实现方法。但是,我今天想了一下,觉得还是需要来理清一下为什么有“代理”的存在(不管是现实中还是代码实现中),因为我刚好学习了装饰模式(Decorator),它使得我想要扩展、增强动态代理的功能。
在上篇文章中,说过动态代理的最明显的优点就是“在不改变原来已有的代码结构的情况下,对原来的‘真实方法’进行扩展、增强其功能,并且可以达到控制被代理对象的行为的目的。”而且这种优点是由 JVM 在程序运行时动态实现的。
但是,我发现如果客户端(Client测试类)先要获得动态代理的实例来实现一定操作时,代理实例的这种功能的扩展已经在服务器端(这里我把服务器端理解为 JVM,因为我们就是从 JVM 中获得动态代理类的实例的)预先地实现了。这种扩展、增强对应于上一篇文章中的代码就是动态代理类中的 invoke() 方法中 before 和 after 注释的这两处中(这两处可以不要同时实现,依具体情况而定)。
也就是说,如果客户端(Client 测试类)一旦获得动态代理类的实例,那么该代理实例中就一定包含了 before 和 after 中的扩展、增强功能。这似乎显得有点“强制性”了,因为如果客户端(Client 测试类)仅仅只需要一个原始的操作,并不希望代理类预先进行扩展,那么这种所谓的动态、灵活就变得不是很受欢迎了。
我们应该让客户端在使用之前才来确定是否需要增强,但是我们总不能在服务器端预先提供好几个不同的动态代理,同时也不可能设计几个继承了动态代理类(DynamicProxy 类)并覆盖了其 request() 方法的子类吧。为了说明我认为动态代理还不够完善、合理,说了这么多现在终于引出重点了,呼呼…
装饰模式(Decorator):动态地给一个对象添加一些额外的职责,就增加功能来说,装饰 模式相比生成子类更为灵活。
使用场景:
1、在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责;
2、处理那些可以撤消的职责;
3、当不能采用生成子类的方法进行扩充时。
下面是装饰模式的通用类图:
这里给出原来上篇文章中的动态代理的类图如下:
为了对达到上面所说的目标,修改类图,其中增加一个ProxyFactory类,它是用于提供动态代理实例的静态工厂类,旨在减少 Client 测试类中的代码。我们将上面两个类图进行整合,如下:其中的 RealSubject类 相当于上面的 ConcreteComponent类,AbstractSubject接口相当于 Component 抽象类,具体类图如下:
具体代码实现如下:
主题相关类:
- //抽象主题类,这里不能用abstract抽象类,一定要是interface
- interface AbstractSubject {
- public abstract void request();
- }
- // 真实主题类,即被代理类
- class RealSubject implements AbstractSubject {
- public void request() {
- System.out.println("RealSubject's request() ...");
- }
- }
装饰相关类:
- // 抽象装饰类,一定要以自己的父类、父接口为一个属性
- abstract class Decorator implements AbstractSubject {
- protected AbstractSubject subject = null;
- public Decorator(AbstractSubject subject) {
- this.subject = subject;
- }
- }
- // 具体装饰类01
- class ConcreteDecorator01 extends Decorator {
- public ConcreteDecorator01(AbstractSubject subject) {
- super(subject); //调用父类装饰类的构造器
- }
- /**
- * 覆盖继承树上的接口中的request()方法,用于装饰原对象
- */
- public void request() {
- System.out.println("第一层装饰 ... 装饰在原主题之前");
- super.subject.request();
- }
- }
- // 具体装饰类02
- class ConcreteDecorator02 extends Decorator {
- public ConcreteDecorator02(AbstractSubject subject) {
- super(subject); //调用父类装饰类的构造器
- }
- /**
- * 覆盖继承树上的接口中的request()方法,用于装饰原对象
- */
- public void request() {
- super.subject.request();
- System.out.println("第二层装饰 ... 装饰在原主题之后");
- }
- }
动态代理类:
- // 动态代理类,实现InvocationHandler接口
- class DynamicProxy implements InvocationHandler {
- // 被代理类的实例
- Object obj = null;
- // 将被代理者的实例传进动态代理类的构造函数中
- public DynamicProxy(Object obj) {
- this.obj = obj;
- }
- /**
- * 覆盖InvocationHandler接口中的invoke()方法
- *
- * 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到
- * 控制被代理对象的行为,下面的before、after就是我们可以进行特殊 代码切入的扩展点了。
- */
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- /*
- * before :doSomething();
- */
- System.out.println("动态代理为真实主题添加一个方法 ...");
- Object result = method.invoke(this.obj, args);
- /*
- * after : doSomething();
- */
- return result;
- }
- }
获取动态代理实例的静态工厂类:
- //提供动态代理实例的静态工厂类
- class ProxyFactory {
- /**
- * @param realSubject :指定需要代理的真实主题类的实例
- * @return proxy :代理的实例
- */
- public static AbstractSubject getProxy(AbstractSubject realSubject) {
- // 获得被代理类的类加载器,使得JVM能够加载并找到被代理类的内部结构,以及已实现的interface
- ClassLoader loader = realSubject.getClass().getClassLoader();
- // 获得被代理类已实现的所有接口interface,使得动态代理类的实例
- Class<?>[] interfaces = realSubject.getClass().getInterfaces();
- // 用被代理类的实例创建动态代理类的实例,用于真正调用处理程序
- InvocationHandler handler = new DynamicProxy(realSubject);
- /*
- * 使用java.lang.reflect.Proxy类中的静态方法newProxyInstance()获得代理的实例
- *
- * loader : 被代理类的类加载器 interfaces :被代理类已实现的所有接口,而这些是动态代理类要实现的接口列表 handler
- * : 用被代理类的实例创建动态代理类的实例,用于真正调用处理程序
- *
- * return :返回实现了被代理类所实现的所有接口的Object对象,即动态代理,需要强制转型
- */
- AbstractSubject proxy = (AbstractSubject) Proxy.newProxyInstance(
- loader, interfaces, handler);
- return proxy;
- }
- }
测试类:
- // 测试类
- public class Client {
- public static void main(String[] args) {
- // 被代理类的实例
- AbstractSubject realSubject = new RealSubject();
- // 通过静态工厂获取动态代理的实例
- AbstractSubject proxy = ProxyFactory.getProxy(realSubject);
- // 装饰之前打印出该代理实例的名称
- System.out.println("装饰前:" + proxy.getClass().getName());
- // 装饰前使用代理实例进行原始操作
- proxy.request();
- System.out.println("\n第一次装饰之后的效果如下:");
- proxy = new ConcreteDecorator01(proxy);
- System.out.println("\n名称:" + proxy.getClass().getName());
- proxy.request();
- System.out.println("\n第二次装饰之后的效果如下:");
- proxy = new ConcreteDecorator02(proxy);
- System.out.println("\n名称:" + proxy.getClass().getName());
- proxy.request();
- }
- }
测试之前记得在上面的类文件中导入相关的包,如下:
import java.lang.reflect.InvocationHandler;
|
测试结果:
装饰前:DesignPattern.proxy.dynamicProxy.$Proxy0
第一次装饰之后的效果如下:
名称:DesignPattern.proxy.dynamicProxy.ConcreteDecorator01
第二次装饰之后的效果如下:
名称:DesignPattern.proxy.dynamicProxy.ConcreteDecorator02 |
在上面的测试类中,我们先通过静态工厂类获得动态代理的实例,然后再根据客户的需要,更加灵活地装饰该动态代理的实例,可以在调用真实主题之前扩展,也可以是之后,总之目的就是使其根据客户的意愿的定制特定功能。
此外,测试结果中给出了装饰前后的动态代理实例的名称,我们发现在客户端中一旦装饰了该动态代理的实例之后,就会同时改变其具体“身份”,毕竟被装饰过“外在形象”也就会跟着改变。
跟现实生活中一样,所谓装饰,就跟我们穿衣服一样,穿不同的衣服就会给他人展现出不同的外在美,呵呵…
其实装饰模式是我在学习 Java IO 中帮助最大的类,之前都被 IO 包中繁杂的类给弄得晕乎乎的,一层层地包装,都不知道那样写代码是什么目的,很难理解…现在倒好了,因为 IO 包中的类就是大量地运用了装饰模式,程序员需要怎样的输入输出,自己动手将其包装一下即可。