代理模式(五)

       按照代理的创建时期,代理类可以分为静态代理和动态代理。

       1.动态代理:在程序运行时,运用反射机制动态创建而成。

       2.静态代理:由程序员创建或特定工具自动生成源代码,再对其编译,在程序运行前,代理类的.class文件就已经存在了。

       两者对比:静态代理只能代理一种类型的被代理类,换个类型的就不行了,这需要动态代理。

       接下来我们以智能引用代理为例,分别采用静态代理和动态代理的方式实现。

继承与聚合的方式实现静态代理

       静态代理的代理和被代理对象在代理之前都是确定的。他们都实现相同的接口或者继承相同的抽象类。

       示例演示:

       假设一辆小车有一个行驶的方法,我们通过代理实现这个行驶的方法,同时增加记录行驶时间的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 首先定义接口Moveable.java:
public interface Moveable {
void move();
}

// 然后定义Car.java类并实现Moveable接口:
public class Car implements Moveable throws Exception{

@Override
public void move() {
System.out.println("汽车行驶中");
Thread.sleep(new Random().nextInt(1000));
}
}

通过继承的方式定义Car2来实现静态代理

1
2
3
4
5
6
7
8
9
10
11
12
public class Car2 extends Car {

@Override
public void move() {
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
super.move();
long endtime = System.currentTimeMillis();
System.out.println("汽车停止行驶,行驶时间:"
+ (endtime - starttime) + "毫秒");
}
}

通过聚合的方式定义Car3来实现静态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Car3 implements Moveable {

public Car3(Car car) {
super();
this.car = car;
}

private Car car;

@Override
public void move() {
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
car.move();
long endtime = System.currentTimeMillis();
System.out.println("汽车停止行驶,行驶时间:"
+ (endtime - starttime) + "毫秒");
}

}

测试代码与结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Client {

/**
* 测试类
*/
public static void main(String[] args) {
// Car car = new Car();
// car.move();
//使用继承方式实现
// Moveable m = new Car2();
// m.move();
//使用聚合方式实现
Car car = new Car();
Moveable m = new Car3(car);
m.move();
}

}

       测试结果如下:

1
2
3
汽车开始行驶
汽车行驶中
汽车停止行驶,行驶时间:373毫秒

两种方式的比较

       静态代理实现有如下两种方法:

       (1)继承法:代理类直接继承被代理类,实现其原有方法,并添加一些额外功能。

       (2)聚合方法:代理类实现相同的功能接口(很重要,相同接口,不同代理也可以进行相互代理)并在内声明一个被代理类的对象(类似封装),通过内部对象实现其原有方法,并添加额外功能。

       那么继承法和聚合法何者更优呢?结论是聚合比继承更适合实现代理。

       如果我们要实现功能的叠加,比如增加方法运行时间处理,增加权限管理,增加日志处理等,或者调整这些功能的实现顺序,如果有一百种实现顺序,我们用继承法就要实现100个代理子类,这显然是不现实的,如果需要修改也是极其繁琐的。而如果使用聚合法,只要一个代理类管理一个功能,在维护和调整顺序上都是最优的。

聚合方式的静态代理,实现功能的叠加

       下面我们实现汽车行驶过程中记录日志和行驶时间的功能。

记录行驶时间的代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CarTimeProxy implements Moveable {

public CarTimeProxy(Moveable m) {
super();
this.m = m;
}

private Moveable m;

@Override
public void move() {
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
m.move();
long endtime = System.currentTimeMillis();
System.out.println("汽车停止行驶,行驶时间:"
+ (endtime - starttime) + "毫秒");
}

}

记录行驶日志的代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CarLogProxy implements Moveable {

public CarLogProxy(Moveable m) {
super();
this.m = m;
}

private Moveable m;

@Override
public void move() {
System.out.println("日志开始");
m.move();
System.out.println("日志结束");
}

}

测试

       先记录日志再记录时间

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {

/**
* 测试类
*/
public static void main(String[] args) {
Car car = new Car();
CarTimeProxy ctp = new CarTimeProxy(car);
CarLogProxy clp = new CarLogProxy(ctp);
clp.move();
}

}

       测试结果如下:

1
2
3
4
5
日志开始
汽车开始行驶
汽车行驶中
汽车停止行驶,行驶时间:653毫秒
日志结束

       通过将记录时间的实例传递给记录日志的实例,实现了先记录日志再记录时间的操作。如果想实现先记录时间再记录日志,将两个实例对象的传递交换一下就能够实现想要的操作。
测试结果如下:

1
2
3
4
5
汽车开始行驶
日志开始
汽车行驶中
日志结束
汽车停止行驶,行驶时间:13毫秒

       代码详见 PatternTest

参考资料:
BestWZR 模式的秘密——代理模式
David 模式的秘密——代理模式

Fork me on GitHub