Chinaunix首页 | 论坛 | 博客
  • 博客访问: 744888
  • 博文数量: 225
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2722
  • 用 户 组: 普通用户
  • 注册时间: 2013-02-03 17:32
文章分类

全部博文(225)

文章存档

2019年(7)

2018年(16)

2017年(1)

2016年(26)

2015年(41)

2014年(15)

2013年(119)

我的朋友

分类: Java

2013-12-05 11:19:50

代理模式与装饰器模式有何区别?

我想有必要对此问题谈一下我的个人理解,若有误导的之处,还请大家指正!

代理模式(Proxy 模式)可理解为:我想做,但不能做,我需要有一个能干的人来帮我做。

装饰器模式(Decorator 模式)可理解为:我想做,但不能做,我需要有各类特长的人来帮我做,但我有时只需要一个人,有时又需要很多人。

它们的区别就是,Proxy 模式需要的是一个能人,而 Decorator 模式需要的是一个团队。

有些情况下,拥有了一个团队,会更加利于工作分工,而不至于将所有的事情,都让这个能人来干,他终将有一天会 hold 不住的。但有些情况下,人多了反而不好,只需要一个能人就行了。

如果这个比喻不太恰当的话,我就要拿出我的杀手锏了,用代码来说话。

我们先来回忆一下这两段经典的代码,一个接口,一个它的实现类。

1 public interface Greeting {
2  
3     void sayHello(String name);
4 }
1 public class GreetingImpl implements Greeting {
2  
3     @Override
4     public void sayHello(String name) {
5         System.out.println("Hello! " + name);
6     }
7 }

可以使用 Proxy 类来代理 GreetingImpl 类做点事情:

01 public class GreetingProxy implements Greeting {
02  
03     private GreetingImpl greetingImpl;
04  
05     public GreetingProxy(GreetingImpl greetingImpl) {
06         this.greetingImpl = greetingImpl;
07     }
08  
09     @Override
10     public void sayHello(String name) {
11         before();
12         greetingImpl.sayHello(name);
13     }
14  
15     private void before() {
16         System.out.println("Before");
17     }
18 }

只需保证 GreetingProxy 与 GreetingImpl 实现同一个接口 Greeting,并通过构造方法将 GreetingImpl 温柔地射入 GreetingProxy 的身体之中,那么,GreetingProxy 就可以完全拥有 GreetingImpl 了。可以在帮它做正事儿之前,先干点别的事情,比如这里的 before() 方法。想干点什么就干点什么,只要您喜欢,它就喜欢。(此处省略一千字)

以上就是 Proxy 模式,可以认为 GreetingProxy 包装了 GreetingImpl,那么,我们就应该怎样来使用呢?

1 public class ClientProxy {
2  
3     public static void main(String[] args) {
4         Greeting greeting = new GreetingProxy(new GreetingImpl());
5         greeting.sayHello("Jack");
6     }
7 }

很爽吧?下面用一张类图来表达我此时此刻的感觉:

 

可见,GreetingProxy 是通过“组合”的方式对 GreetingImpl 进行包装,并对其进行功能扩展。这样,无需修改 GreetingImpl 的任何一行代码,就可以完成它想要做的事情。

说的高深一点,这就是“开闭原则”(可不是一开一闭的意思哦),它是设计模式中一条非常重要的原则,意思就是“对扩展开放,对修改封闭”。没错,我们确实 是提供了 GreetingProxy 类来扩展 GreetingImpl 的功能,而并非去修改 GreetingImpl 原有的代码。这就是超牛逼的“开闭原则”了,每个开发人员都需要铭记在心!还需要知道的就是扩展并非只有“继承”这一种方式,这里用到的“组合”也是一种 扩展技巧。

其实,以上使用 Proxy 模式实现了 AOP 理论中的 Before Advice(前置增强)功能。如果用户现在来了一个需求,需要在 sayHello 完事之后再记录一点操作日志。那么,我们此时最简单的方法就是给 GreetingProxy 增加一个 after() 方法,代码如下:

01 public class GreetingProxy implements Greeting {
02  
03     private GreetingImpl greetingImpl;
04  
05     public GreetingProxy(GreetingImpl greetingImpl) {
06         this.greetingImpl = greetingImpl;
07     }
08  
09     @Override
10     public void sayHello(String name) {
11         before();
12         greetingImpl.sayHello(name);
13         after();
14     }
15  
16     private void before() {
17         System.out.println("Before");
18     }
19  
20     private void after() {
21         System.out.println("After");
22     }
23 }

这样做确实可以实现需求,但您要知道,需求是永无止境的,这个 Proxy 类将来可能会非常庞大,要干的事情会越来越多。一下子是日志记录,一下子是事务控制,还有权限控制,还有数据缓存。把所有的功能都放在这个 Proxy 类中是不明智的,同时这也违反了“开闭原则”。

作为一个牛逼的架构师,有必要来点炫的东西,让那帮程序员小弟们对您投来崇拜的目光。

用 Decorator 模式吧!

先来一张牛图:

搞了一个抽象类 GreetingDecorator 出来,确实挺抽象的,它就是传说中的“装饰器”了,也实现了 Greeting 接口(与 Proxy 模式相同),但却有两点不同:

  1. 在装饰器中不是组合实现类 GreetingImpl,而是组合它的接口 Greeting。
  2. 下面通过两个 Decorator 的实现类(也就是具体装饰器),来提供多种功能的扩展。

我们不再需要一个能人,而需要一个团队!

如果要加入日志记录功能,可以搞一个日志记录的装饰器;如果要加入事务控制功能,也可以再搞一个事务控制的装饰器;...

想怎么装饰就怎么装饰,这就像您买了一套新房,现在都是毛坯的,您可以刷漆,也可以贴纸,还可以画画,当然可以又刷漆、又贴纸、又画画。

屁话少说,上代码吧!

先来看看这个装饰器:

01 public abstract class GreetingDecorator implements Greeting {
02  
03     private Greeting greeting;
04  
05     public GreetingDecorator(Greeting greeting) {
06         this.greeting = greeting;
07     }
08  
09     @Override
10     public void sayHello(String name) {
11         greeting.sayHello(name);
12     }
13 }

以上是一个很干净的装饰器,没有任何的增强逻辑,只是简单的通过构造方法射入了 Greeting 对象,然后调用它自己的 sayHello() 方法,感觉啥也没干一样。

当然,GreetingDecorator 只是一个抽象的装饰器,要想真正使用它,您得去继承它,实现具体的装饰器才行。

第一个具体装饰器 GreetingBefore:

01 public class GreetingBefore extends GreetingDecorator {
02  
03     public GreetingBefore(Greeting greeting) {
04         super(greeting);
05     }
06  
07     @Override
08     public void sayHello(String name) {
09         before();
10         super.sayHello(name);
11     }
12  
13     private void before() {
14         System.out.println("Before");
15     }
16 }

第二个具体装饰器 GreetingAfter:

01 public class GreetingAfter extends GreetingDecorator {
02  
03     public GreetingAfter(Greeting greeting) {
04         super(greeting);
05     }
06  
07     @Override
08     public void sayHello(String name) {
09         super.sayHello(name);
10         after();
11     }
12  
13     private void after() {
14         System.out.println("After");
15     }
16 }

需要注意的是,在具体装饰器的构造方法中调用了父类的构造方法,也就是把 Greeting 实例射进去了。在具体装饰器中,完成自己应该完成的事情。真正做到了各施其责,而不是一人包揽。

我们可以这样来用装饰器:

1 public class ClientDecorator {
2  
3     public static void main(String[] args) {
4         Greeting greeting = new GreetingAfter(new GreetingBefore(new GreetingImpl()));
5         greeting.sayHello("Jack");
6     }
7 }

先 new GreetingImpl,再 new GreetingBefore,最后 new GreetingAfter。一层裹一层,就像洋葱一样!但不同的是,裹的顺序是可以交换,比如,先 new GreetingAfter,再 new GreetingBefore。

这种创建对象的方式是不是非常眼熟呢?没错!在 JDK 的 IO 包中也有类似的现象。

比如:想读取一个二进制文件,可以这样获取一个输入流:

1 InputStream input = new DataInputStream(new BufferedInputStream(new FileInputStream("C:/test.exe")));

其实看看 IO 的类图,就一目了然了,它就用到了 Decorator 模式:

IO 包是一个很强大的包,为了使表达更加简化,以上仅提供了 IO 中的部分类。

到此,想必您已经了解到 Proxy 模式与 Decorator 模式的本质区别了吧?

这里两个模式都是对类的包装,在不改变类自身的情况下,为其添加特定的功能。若这些功能比较单一,可考虑使用 Proxy 模式,但对于功能较多且需动态扩展的情况下,您不妨尝试一下 Decorator 模式吧!


阅读(863) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~