JAVA静态代理为什么用聚合用接口

来源:互联网 发布:macbook办公软件 编辑:程序博客网 时间:2024/04/28 05:53

         学习静态代理时,网上找到了挺多例子,但是作为一个菜鸟的我,实在没能弄明白,为什么一定要弄个接口出来,委托类必须实现它,因为按照网上搜到的例子,实在看不多使用接口的必要性。于是看了马士兵老师的视频后,终于明白了,现在把马士兵老师对静态代理的讲解整理一下。

 

      代理模式是常用的Java 设计模式, 它的特征是代理类与委托类有同样的接口。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

     看到上面的概念,我第一的反应就是,这和filter(访问前和访问后做一些处理)及spring 的 AOP(对方法的访问前或访问后做一些处理)。

 

        代理类和委托类存在关联关系。关联关系包括聚合、组合。关于聚合、组合网上有很多资料详细介绍,这里简单说一下。

        从生命周期来看,聚合不同步死亡。比如类A中有一个成员变量指向类B,类A死了,但是类B不一定死。用代码来简单表示一下,如下。

         

package test;public class A {private B b;public A(B b){this.b=b;}}package test;public class B {}package test;/** * 测试类 * */public class Test {public static void main(String[] args){B b=new B();A a=new A(b);a=null;//a指向的实例对象A将会被GC,A中的成员变量b也会GC,  //但是它指向的实例对象B,并不会被GC,因为Test类的main方法还没结束,         //仍然有局部变量b指向它(实例对象B)//.......//......}}


 

     组合,就是同死,比如A中包含了一个B,A死了,则B也会死。用代码简单表示,如下。

package test;public class A {private B b;public A(){this.b=new B();}}package test;public class B {}


       一句话:如果B不是在A中new的,而A又有指向B的成员变量,则A,B是聚合关系;如果B是在A中new出来的,则A,B的关系就是组合。

       好了,回归到静态代理。比如现在有一个TankImpl类实现了Moveable接口(直接拿马士兵老师的例子讲解了。。。嘿嘿)。

 

package proxy;public interface Moveable {     public void move();}

package proxy;

import java.util.Random;

public class TankImpl implements Moveable { @Override public void move() {  System.out.println("正在移动中............");  try {   Thread.sleep(new Random().nextInt()*10000);  } catch (InterruptedException e) {   // TODO Auto-generated catch block   e.printStackTrace();  } }}

 

     现在有个需求,在不改变TankImpl任何代码下,计算move()执行了多长时间。马士兵老师列出了3中方法,继承、组合、聚合。

package proxy;/** * 使用继承计算tank移动的时间 */public class Tank1 extends TankImpl{  @Overridepublic void move(){long start=System.currentTimeMillis();super.move();long end = System.currentTimeMillis();System.out.println("移动了"+(end-start)+" 毫秒");}}package proxy;/** *使用组合计算tank移动的时间 */public class Tank2 implements Moveable{private Moveable t=new TankImpl();public Tank2(){}public void move(){long start=System.currentTimeMillis();t.move();long end = System.currentTimeMillis();System.out.println("移动了"+(end-start)+" 毫秒");}}package proxy;/** *使用聚合计算tank移动时间 */public class Tank3 implements Moveable{private Moveable t;public Tank3(Moveable t){this.t=t;}public void move(){long start=System.currentTimeMillis();t.move();long end = System.currentTimeMillis();System.out.println("移动了"+(end-start)+" 毫秒");}}package proxy;/** *测试类  */public class Client {public static void main(String[] args){System.out.println("=======继承计算tank移动时间========");new Tank1().move();System.out.println("=======组合计算tank移动时间========");new Tank2().move();System.out.println("=======聚合计算tank移动时间========");new Tank3(new TankImpl()).move();}}


运行结果


=======继承计算tank移动时间========
正在移动中............
移动了6006 毫秒
=======组合计算tank移动时间========
正在移动中............
移动了8264 毫秒
=======聚合计算tank移动时间========
正在移动中............
移动了6956 毫秒

 

     从运行结果看,3种方法都实现了需求。那么比较一些这3种方法的灵活性。

      假如现在工厂又生产了一辆新坦克TankImpl2 implements Moveable,现在也需要测试它的移动时间。如果使用继承,那么就只能再写一个新类继承TankImpl2;使用组合,也是需要再写一个新类,在它内部new TankImpl2();使用聚合,你发现,不需要写新类,只要new TankImpl2传给Tank3即可。结论聚合更灵活。

 

       假如现在又有一个需求,在不改变Tank1,Tank2,Tank3的前提下,需要在计算移动时间前,写个日志,说坦克准备移动及坦克移动结束。也许你会这样写。

package proxy;/** * 使用继承写日志及计算tank移动的时间 */public class Tank11 extends Tank1{  @Overridepublic void move(){System.out.println("坦克准备移动.......");super.move();System.out.println("坦克移动结束..........");}}package proxy;/** *使用组合写日志及计算tank移动的时间 */public class Tank21 {private Moveable t=new Tank2();public Tank21(){}public void move(){System.out.println("坦克准备移动.......");t.move();System.out.println("坦克移动结束..........");}}package proxy;/** *使用聚合写日志计算tank移动时间 */public class Tank31 implements Moveable{private Moveable t;public Tank31(Moveable t){this.t=t;}public void move(){System.out.println("坦克准备移动.......");t.move();System.out.println("坦克移动结束..........");}}package proxy;/** *测试类  */public class Client {public static void main(String[] args){System.out.println("=======继承写日志计算tank移动时间========");new Tank11().move();System.out.println("=======组合写日志计算tank移动时间========");new Tank21().move();System.out.println("=======聚合写日志计算tank移动时间========");new Tank31(new Tank3(new TankImpl())).move();}}

运行结果

=======继承写日志计算tank移动时间========
坦克准备移动.......
正在移动中............
移动了2634 毫秒
坦克移动结束..........
=======组合写日志计算tank移动时间========
坦克准备移动.......
正在移动中............
移动了7060 毫秒
坦克移动结束..........
=======聚合写日志计算tank移动时间========
坦克准备移动.......
正在移动中............
移动了878 毫秒
坦克移动结束..........

     从运行结果看,3中方法也都实现了需求,都需写新一个类,似乎不确定谁更灵活。那假如现在要求先计算时间,再写日志呢?如果用继承,那么就得再写新类,用组合也要再写新类,但是用聚合,就不需要了,只要把Tank31传给Tank3就OK了,就是在测试类中new Tank31(new Tank3(new TankImpl())).mvoe改为new Tank3(new Tank31(new TankImpl())).move(); 只要在测试类中改变写顺序就OK了。如果还有新需求,并且需要按照不同的顺序测试时,聚合的灵活性就更突出了。

 

             细心的你,也许也发现了,把上面例子的接口换成抽象类,是否也OK了。这就涉及到抽象类和接口的使用场景了。可以在网上搜一下,唉。。。才正式工作一年的俺,一个个初级小菜鸟了,实在太多不懂了。

 

 

       其实这3种方法都可以说是静态代理,但是平时说的静态代理指的是使用聚合的静态代理,因为它更灵活。

       静态代理的缺点也很明显,从上面例子可以看出来,代理类和委托类必须实现相同的接口。假如接口中新增了一个方法,同时对新方法也要像旧方法那样计算时间,写日志等操作,那么委托类和代理类都要实现新方法,同时代理类还要对新类写计算时间方法,写日志方法。这会很繁琐,代码也需要经常修改,维护代价高。

      那么有没有这样的代理,不管接口是否新增了方法,代理类都不需要做任何修改呢?有,那就是JDK动态代理。下一章,再浅谈JDK的动态代理。


        

原创粉丝点击