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

本文通过具体示例详细解释了静态代理的原理,对比了继承、组合和聚合三种方式,并阐述了聚合方式的优势及其应用场景。

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

 

      代理模式是常用的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{
  
	@Override
	public 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{
  
	@Override
	public 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的动态代理。


 
 
 
 
 
 
 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值