大家都知道设计模式中一个比较难理解的模式--动态代理模式,下面我们来通过一步一步完善一个工程来学习动态代理。
首先我们创建一个JavaProject,名字为"ProxyTest"。
创建一个类Tank.java,是一个坦克类,然后我们创建一个接口Moveable.java
Moveable.java:
package cn.edu.hpu.proxy; public interface Moveable { void move(); }
Tank类实现Moveable接口
Tank.java:
package cn.edu.hpu.proxy; import java.util.Random; public class Tank implements Moveable{ @Override public void move() { System.out.println("坦克正在移动中..."); try { //睡眠10秒以内的随机时间,表示坦克正在移动中 Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
下面问题来了:
我想知道一个方法的运行时间,如何得到?
大家很容易想到的是,在方法刚刚执行后和就要结束之前计算一下当前时间,然后相减获得执行时间:
@Override public void move() { long start=System.currentTimeMillis(); System.out.println("坦克正在移动中..."); try { //睡眠10秒以内的随机时间,表示坦克正在移动中 Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } long end=System.currentTimeMillis(); System.out.println("运行时间:"+(end-start)+"ms"); }
那么,如果不允许修改move中的方法呢?比如说这个方法是别人的包里提供给你的,是人家已经写好的源代码,就编译了class给你,就没有给你源码,你没法在源码上修改怎么办?解决这件事情之前,我们先写一个测试类:
package cn.edu.hpu.proxy; public class Client { public static void main(String[] args) { Moveable m=new Tank(); m.move(); } }
不要求计算move()方法执行的时间(JDK为其准备的时间等...),要计算内部代码执行的时间。
我们再写一个Tank2,继承Tank
package cn.edu.hpu.proxy; public class Tank2 extends Tank{ @Override public void move() { long start=System.currentTimeMillis(); super.move(); long end=System.currentTimeMillis(); System.out.println("运行时间:"+(end-start)+"ms"); } }
用继承来实现把原来的方法前后加一些逻辑,这是一种方式,是没有问题的。
除了上面这种方式之外,还有另外一种方式:
写一个Tank3,实现和Tank一样的接口
package cn.edu.hpu.proxy; public class Tank3 implements Moveable{ Tank t; public Tank3(Tank t) { super(); this.t = t; } @Override public void move() { long start=System.currentTimeMillis(); t.move(); long end=System.currentTimeMillis(); System.out.println("运行时间:"+(end-start)+"ms"); } }
上面一个使用继承来实现,一个是一个类存有另外一个类的对象,叫聚合。
这两种都是简单的代理的实现。
现在请问大家:认为这两种方式那种更好?为什么?
聚合好。为什么呢?
下面来看看。
除了时间的代理之外,还有其他的各种各样的代理,如在方法开始前后做日志记录,或加一段事务控制,或检查运行权限等。
由于Tank3它是记录时间的,而且是Tank的一个代理类,所以我们给它换一个名字,叫"TankTimeProxy"类。
package cn.edu.hpu.proxy; public class TankTimeProxy implements Moveable{ Tank t; public TankTimeProxy(Tank t) { super(); this.t = t; } @Override public void move() { long start=System.currentTimeMillis(); System.out.println("开始时间:"+start+"ms"); t.move(); long end=System.currentTimeMillis(); System.out.println("运行时间:"+(end-start)+"ms"); } }
我们再写一个类是对Tank类加日志信息的日志代理类:
package cn.edu.hpu.proxy; public class TankLogProxy implements Moveable{ Tank t; public TankLogProxy(Tank t) { super(); this.t = t; } @Override public void move() { System.out.println("坦克准备移动..."); t.move(); System.out.println("坦克移动结束..."); } }
下面我们要对日志进行叠加,规定先先记录时间,再记录日志,怎么办?需要在写一个Tank3去继承Tank2(里面已经包含了时间记录代理),然后把日志记录代码加在move方法前后。如果规定先记录日志,再记录时间,怎么办?又要创建一个Tank4,继承Tank类,然后把日志记录代码加在move方法前后,之后再创建Tank5继承自Tank4,在在move方法前后加上时间记录代码.....你会发现,用继承的方式实现代理的话,这个类会无限制的增加下去,各种各样的代理功能如果想实现叠加的话,这个类要无限的往下继承,无边无际了...
所以,回到刚刚的话题,继承并不是好的实现代理的方式,聚合比较合适。
接下来看看聚合的优点。首先我们把TankTimeProxy和TankLogProxy中的Tank引用类改成Moveable接口(包括构造函数的参数类型)。
拿出TankTimeProxy与TankLogProxy看看:
TankTimeProxy:
package cn.edu.hpu.proxy; public class TankTimeProxy implements Moveable{ Moveable t; public TankTimeProxy(Moveable t) { super(); this.t = t; } @Override public void move() { long start=System.currentTimeMillis(); System.out.println("开始时间:"+start+"ms"); t.move(); long end=System.currentTimeMillis(); System.out.println("运行时间:"+(end-start)+"ms"); } }
TankLogProxy:
package cn.edu.hpu.proxy; public class TankLogProxy implements Moveable{ Tank t; public TankLogProxy(Tank t) { super(); this.t = t; } @Override public void move() { System.out.println("坦克准备移动..."); t.move(); System.out.println("坦克移动结束..."); } }
我们其中的引用类Moveable t;既可以是实现了Moveable接口的Tank类,又可以是实现了Moveable接口的代理类,那么,代理之间的嵌套是不是可以实现呢?可以,加入我们想在日志代理外面嵌套时间代理,即先记录时间后记录日志,那么我们直接把引用类Moveable t;的实现指向日志代理类TankLogProxy即可。
即:
Moveable t=new TankTimeProxy(new TankLogProxy(new Tank())); t.move();
运行TankTimeProxy的move方法时,
@Override public void move() { long start=System.currentTimeMillis(); System.out.println("开始时间:"+start+"ms"); //此时t指向TankLogProxy类,调用的是TankLogProxy类的move方法 t.move(); long end=System.currentTimeMillis(); System.out.println("运行时间:"+(end-start)+"ms"); }
我们知道TankLogProxy类的move方法是这样的(也就是上面的t.move();):
@Override public void move() { System.out.println("坦克准备移动..."); //这里的t指向的是Tank,所以执行的是Tank的move方法 t.move(); System.out.println("坦克移动结束..."); }
把TankTimeProxy的move方法中的t.move();解刨出来就是:
</pre><pre name="code" class="java">@Override public void move() { long start=System.currentTimeMillis(); System.out.println("开始时间:"+start+"ms"); System.out.println("坦克准备移动..."); //Tank的move方法 t.move(); System.out.println("坦克移动结束..."); long end=System.currentTimeMillis(); System.out.println("运行时间:"+(end-start)+"ms"); }
注意:此时Tank类(没加任何代理,只有最原始的移动方法):
package cn.edu.hpu.proxy; import java.util.Random; public class Tank implements Moveable{ @Override public void move() { System.out.println("坦克正在移动中..."); try { //睡眠10秒以内的随机时间,表示坦克正在移动中 Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
测试:
package cn.edu.hpu.proxy; public class Client { public static void main(String[] args) { Moveable t=new TankTimeProxy(new TankLogProxy(new Tank())); t.move(); } }
测试结果:
开始时间:1435309953538ms
坦克准备移动...
坦克正在移动中...
坦克移动结束...
运行时间:1529ms
此时就实现了时间和日志代理的嵌套,当然还可以嵌套更多的代理逻辑,是不是比继承方便多了?
以后为了方便管理,也可以将代理逻辑的先后通过配置文件来配置。
关键就是实现统一接口。
我们接下来继续讨论(以后只考虑TankTimeProxy)。如果Moveable接口里还有一个方法,stop()停止方法,Tank必须实现这个方法,那么我要知道stop()方法运行的时间,还要加之前写的时间代理代码:
首先是Moveable接口
package cn.edu.hpu.proxy; public interface Moveable { void move(); void stop(); }
Tank类
package cn.edu.hpu.proxy; import java.util.Random; public class Tank implements Moveable{ @Override public void move() { System.out.println("坦克正在移动中..."); try { //睡眠10秒以内的随机时间,表示坦克正在移动中 Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void stop() { System.out.println("坦克停止中..."); } }
TankTimeProxy类
package cn.edu.hpu.proxy; public class TankTimeProxy implements Moveable{ Moveable t; public TankTimeProxy(Moveable t) { super(); this.t = t; } @Override public void move() { long start=System.currentTimeMillis(); System.out.println("开始时间:"+start+"ms"); t.move(); long end=System.currentTimeMillis(); System.out.println("运行时间:"+(end-start)+"ms"); } @Override public void stop() { long start=System.currentTimeMillis(); System.out.println("开始时间:"+start+"ms"); t.stop(); long end=System.currentTimeMillis(); System.out.println("运行时间:"+(end-start)+"ms"); } }
这个时候我们注意了,当我们平时在写程序的时候,如果你发现有一段代码你总是在那里写,这个方法里有,那个方法里也有,这个时候我们就要考虑将这段代码封装起来了。
这里我们发现TankTimeProxy类中有关时间记录的代码有重复,我们将其封装起来:
package cn.edu.hpu.proxy; public class TankTimeProxy implements Moveable{ Moveable t; long start; long end; public TankTimeProxy(Moveable t) { super(); this.t = t; } public void before(){ start=System.currentTimeMillis(); System.out.println("开始时间:"+start+"ms"); } public void after(){ end=System.currentTimeMillis(); System.out.println("运行时间:"+(end-start)+"ms"); } @Override public void move() { this.before(); t.move(); this.after(); } @Override public void stop() { this.before(); t.stop(); this.after(); } }
下面我们考虑一个很难的问题,假如说我想让TankTimeProxy这个代理叫做TimeProxy,意味着它不仅可以计算Tank类的运行方法,还可以把任何一个对象当做被代理对象,可以计算任何代理类的方法运行的时间。(至于为什么会有这种需求,比如说我们又加了一个交通工具Car,我们要对Car计算运行时间的话,要重写一个CarTimeProxy代理,如果还有上百种类型的类,对不同的类实现时间代理,你要写上百种代理类!!!)。
所以我们要实现一种"通用"时间代理,可以对任意对象代理。
我们可以使用Java的反射机制,具体方式详见总结02
转载请注明出处:http://blog.csdn.net/acmman/article/details/46827819