装饰模式和代理模式的区别

区别介绍

       装饰模式和代理模式看起来很像。对装饰模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个接口。对代理模式来说,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。此外,不论我们使用哪一个模式,都可以很容易地在真实对象的方法前面或者后面加上自定义的方法。

       实际上,二者的目的不一样,关注的重心不一样。装饰模式关注于在一个对象上动态的添加方法,代理模式关注于控制对对象的访问。装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰者来包裹真实的对象;代理模式是为其它对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

1
2
3
4
代理模式目的:让原有对象被代理,让使用者尽可能的感受不到原有对象,原有对象的行为或额外的动作交由代理对象完成。(完成代理模式的真正意义)
装饰模式目的:让原有对象被增强,我们的目的通常是得到由原有对象被增强后的装饰器对象行为。(完成装饰模式的真正意义)
代理模式关注重心:主要功能不变,代理对象只是帮忙代理或稍加扩展原有对象的行为,功能上主要关心原有对象所具有的行为。(最终主要功能仍然由原有对象决定)
装饰模式关注重心:主要功能增强,使用装饰器目的就是为了增强,功能上更关心装饰增加后的行为。(最终主要功能由装饰对象决定)

网上说法(存疑)

       用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建(new)一个对象的实例。而当我们使用装饰模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。我们可以用另外一句话来总结这些差别:使用代理模式,代理和真实对象之间的的关系通常在编译时就已经确定了,而装饰者能够在运行时递归地被构造。

       代理模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//代理模式
public class Proxy implements Subject{

private Subject subject;
public Proxy(){
//关系在编译时确定
subject = new RealSubject();
}
public void doAction(){
….
subject.doAction();
….
}
}
 
//代理的客户
public class Client{
public static void main(String[] args){
//客户不知道代理委托了另一个对象
Subject subject = new Proxy();

}
}

       装饰模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//装饰模式
public class Decorator implements Component{
private Component component;
public Decorator(Component component){
this.component = component
}
public void operation(){
….
component.operation();
….
}
}

//装饰器的客户
public class Client{
public static void main(String[] args){
//客户指定了装饰者需要装饰的是哪一个类
Component component = new Decorator(new ConcreteComponent());

}
}

结构图比较

       下面是代理模式的结构图:

区别之代理模式

       代理对象Proxy和被代理对象RealSubject都继承了Subject接口。客户端调用Proxy的方法,而Proxy则把具体操作委托给RealSubject执行。下面是代码实现:

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
interface Subject {
void doAction();
}

class RealSubject implements Subject {

@Override
public void doAction() {
System.out.println("RealSubject#doAction");
}
}

class Proxy implements Subject {
private Subject subject;

public Proxy(Subject subject) {
this.subject = subject;
}

@Override
public void doAction() {
subject.doAction();
}
}

public class Client {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
Subject proxy = new Proxy(realSubject);
proxy.doAction();
}
}

       输出如下:

1
RealSubject#doAction

       在Client中,首先创建了一个realSubject 对象,然后创建一个代理对象proxy并且把realSubject对象通过构造器传入进去。最后调用代理对象的doAction,实际执行的是realSubject的对应方法。这里通过构造函数的参数将被代理对象传入到代理中,也可以通过其它方式,如提供一个setSubject方法。

       上面的代理模式,代理对象和被代理对象需要实现相同的接口,所以如果要代理其它接口的对象需要写一个新的代理类。Java提供了动态代理的功能,可以简化我们的代码。

       动态代理可以在运行期生成所需要的代理对象,看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class DynamicProxy implements InvocationHandler {
// 被代理对象的引用
private Object obj;

public DynamicProxy(Object obj) {
this.obj = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(obj, args);
return result;
}
}

       类DynamicProxy实现了InvocationHandler接口,这个接口中有一个invoke方法,被代理的对象的任何方法都是在invoke中调用。下面是Client的代码:

1
2
3
4
5
6
7
8
9
10
11
public class TestDelegate {
public static void main(String[] args) {
Subject realSubject = new RealSubject();

DynamicProxy dynamicProxy = new DynamicProxy(realSubject);
ClassLoader loader = realSubject.getClass().getClassLoader();
Subject proxy = (Subject) Proxy.newProxyInstance(loader, new Class[]{Subject.class}, dynamicProxy);

proxy.doAction();
}
}

       输出如下:

1
RealSubject#doAction

       首先依然是先创建一个需要被代理的对象realSubject,然后把它传入到DynamicProxy的构造函数中。这个dynamicProxy还不是我们需要的代理,毕竟它没有实现Subject接口。下面通过Proxy.newProxyInstance创建了一个Subject对象,也就是最终的代理对象。

       通过动态代理,创建一个实现了InvocationHandler接口的DynamicProxy类,通过这个类可以在运行期为各种对象创建对应的代理,比静态代理方便了很多。

       下面是装饰模式的结构图:

区别之装饰模式

       从上图可以看出,装饰者Decorator与需要被装饰的对象ContcreteComponent实现了相同的接口。具体怎么装饰则由Decorator的子类ConcreteDecorator决定。

       Java中使用装饰者模式的一个典型的例子是I/O对象的创建,比如创建一个BufferedInputStream时:

1
2
InputStream in = ...
InputStream input = new BufferedInputStream(in);

       BufferedInputStream继承于FilterInputStream,这个FilterInputStream相当于装饰者模式中的Decorator,它继承了InputStream接口。BufferedInputStream则是一个具体的装饰类,其它还有DataInputStream以及ByteArrayInputStream等。而传给BufferedInputstream的对象in则是需要被装饰者。装饰者对被装饰者进行了功能的扩展,但是又不需要修改被装饰者的相应代码,符合“开闭原则”,即对于修改是封闭的,对于扩展则是开放的。

       如果是为了给某个类提供更多的功能,继承是一种方案。但是,如果我们的功能有很多种组合,那么为每种组合编写一个继承的类可能需要创建太多的子类。而装饰者模式则可以解决这个问题,只需要为每个功能编写一个装饰类,在运行时组合不同的对象即可实现所需的功能组合。

参考资料:
然则 代理模式和装饰者模式
风雪漫中州 设计模式-代理模式(和装饰模式的真正区别)
shuzhou12 装饰模式与代理模式的区别

Fork me on GitHub