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的动态代理。
- JAVA静态代理为什么用聚合用接口
- java简单静态代理(聚合)
- JDK动态代理为什么必须用接口
- 静态代理模式(java),接口练习
- java [ 静态(接口)代理 ,动态(接口)代理,CglibProxy动态(实体类)代理]
- java中的代理模式 用接口
- java代理静态代理
- 关于java中静态代理与接口的结合
- java多态(接口与实现)之静态代理
- java调用聚合数据接口
- JDK动态代理为什么必须用接口以及与CGLIB的对比
- Java代理之静态代理
- java代理模式---静态代理
- Java静态代理、动态代理
- java静态代理,动态代理
- Java静态代理动态代理
- Java 代理之静态代理
- JAVA代理模式--静态代理
- php复习
- 解决vi/vim中粘贴会在行首多很多缩进和空格的问题
- linux下报错bash: service: command not found
- ERP项目中的后期运维研究
- JaveWeb统计在线人数
- JAVA静态代理为什么用聚合用接口
- VMware下安装的Mac OS X如何修改显示分辨率
- 浅谈数据库索引
- first single char
- 重学C++ 友元函数
- Android NDK 编译过程中遇到错误 exception handling disabled, use
- 互联网信息可信性现状
- mysql数据库索引优缺点及注意事项
- svn2