设计模式(5)——代理模式

本文介绍代理模式的概念和应用。

基本思想和原则

为某个对象提供一个代理以控制对这个对象的访问。

将真正提供服务的类隐藏起来,并使用一个类来代理它。这个代理类在对外提供服务的同时还可以对被代理类做一些增强,比如前置处理和后置处理。

动机

当有一个类不想暴露给外部,但又要对外提供服务时,为了隐藏这个类又让它提供服务,我们可以创建一个代理类,代理类对外暴露的接口和被代理类是一样的,用户可以像使用被代理类一样毫无差别地使用代理类,而实际上真正提供服务的是被代理类,这对用户是完全透明的。

实现

一个静态的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public interface IStaff {
public void serve();
}

public class CustomerServiceStaff implements IStaff {
private IStaff staff = null;

public CustomerServiceStaff(IStaff _staff) {
this.staff = _staff;
}

@Override
public void serve() {
this.answerThePhone();
this.staff.serve();
this.recordTheEvent();
}

private void answerThePhone() {
System.out.println("Answer the phone...");
}

private void recordTheEvent() {
System.out.println("Record the event...");
}
}


public class Engineer implements IStaff {
@Override
public void serve() {
System.out.println("Engineer serve...");
}
}

public class Test {
public static void main(String[] args) {
IStaff engineer = new Engineer();
IStaff customerServiceStaff = new CustomerServiceStaff(engineer);

customerServiceStaff.serve();
}
}

输出如下:

1
2
3
Answer the phone...
Engineer serve...
Record the event...

上面代码中,定义了一个IStaff接口,里面有一个serve方法,还定义了CustomerServiceStaffEngineer两个实现类实现了IStaff接口。用户致电客服人员要求提供系统维护服务,但客服人员并不会自己提供这个服务,而是代理给后方的工程师。在这个体系中,工程师是被代理者,客服人员是代理者。工程师对用户来说是透明的,用户只需要和客服人员沟通就可以获得服务,而不管这个服务是谁提供的。

另外注意到在客服人员服务时,不但将任务委托给工程师,还在任务前接听电话,任务后记录事件。这实际上是对工程师职责的扩展,实现了面向切片编程(Aspect Oriented Programming, AOP),这是一个相当强大的编程概念。这里工程师只要负责自己的工作就好了,一些外部工作都交给客服人员这个代理类来实现

静态代理工作起来当然没问题,不过如果每个被代理类都要实现一个特定的静态代理,也是挺麻烦的。我们更抽象一点,可以实现动态代理。上面实现的静态代理在编译期就知道代理谁了,所以适用性比较窄。动态代理简单来说就是在运行时才决定代理谁。

使用动态代理的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public interface IMachine {
public void work();
}

public class MachineA implements IMachine {
public void work() {
System.out.println("MachineA work!");
}
}

public class MyInvocationHandler implements InvocationHandler {
private Object target = null;

public MyInvocationHandler(Object object) {
this.target = object;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.target, args);
}
}

public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
return (T) Proxy.newProxyInstance(loader, interfaces, h);
}
}

public class Test {
public static void main(String[] args) {
IMachine machine = new MachineA();
InvocationHandler handler = new MyInvocationHandler(machine);

IMachine proxy = DynamicProxy.newProxyInstance(machine.getClass().getClassLoader(), machine.getClass().getInterfaces(), handler);
proxy.work();
}
}

输出如下:

1
MachineA work!

动态代理比较难理解,这里解释一下。

首先创建一个IMachine接口,其中定义了一个work方法,所有具体的机器类都要实现这个方法。每个代理实例都需要一个相关联的调用处理器,当代理实例的方法被调用时,这个处理器会将方法在内部代理给被代理类的实例处理。我们创建一个类MyInvocationHandler实现java.lang.reflect.InvocationHandler接口,它有一个私有变量target,就是被代理类的实例,在构造函数中可以指定被代理类的实例进行初始化,另外还需要实现invoke方法,这个方法是InvocationHandler接口唯一要实现的方法。当在它所关联的代理类实例上调用方法时,invoke方法将被调用,其内部会将方法调用委托给被代理类的实例执行,并返回结果。

当然还需要创建一个动态代理类DynamicProxy,这个类有一个newProxyInstance方法,其内部调用了Proxy.newProxyInstance方法,该方法会返回一个代理类实例,这个代理类实例会将特定的方法调用委托给与它关联的调用处理器来处理。

优点

利用代理模式可以让真正提供服务的类专注与它的逻辑,代理类负责一些琐碎的事情。这种模式的扩展性也很好,代理类可以使用AOP的方式在真正的任务前后做一些处理,比较典型的方式是执行任务前做一些准备,执行任务后做一些清理并记录日志等。