文档章节

Proxy 遇上 Decorator

黄勇
 黄勇
发布于 2013/09/19 16:01
字数 1822
阅读 2541
收藏 84

有些朋友们看过《Proxy 那点事儿》与《AOP 那点事儿》之后,提出了一个很有代表性的问题:

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

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

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

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

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

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

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

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

public interface Greeting {

    void sayHello(String name);
}
public class GreetingImpl implements Greeting {

    @Override
    public void sayHello(String name) {
        System.out.println("Hello! " + name);
    }
}

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

public class GreetingProxy implements Greeting {

    private GreetingImpl greetingImpl;

    public GreetingProxy(GreetingImpl greetingImpl) {
        this.greetingImpl = greetingImpl;
    }

    @Override
    public void sayHello(String name) {
        before();
        greetingImpl.sayHello(name);
    }

    private void before() {
        System.out.println("Before");
    }
}

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

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

public class ClientProxy {

    public static void main(String[] args) {
        Greeting greeting = new GreetingProxy(new GreetingImpl());
        greeting.sayHello("Jack");
    }
}

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

 

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

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

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

public class GreetingProxy implements Greeting {

    private GreetingImpl greetingImpl;

    public GreetingProxy(GreetingImpl greetingImpl) {
        this.greetingImpl = greetingImpl;
    }

    @Override
    public void sayHello(String name) {
        before();
        greetingImpl.sayHello(name);
        after();
    }

    private void before() {
        System.out.println("Before");
    }

    private void after() {
        System.out.println("After");
    }
}

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

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

用 Decorator 模式吧!

先来一张牛图:

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

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

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

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

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

屁话少说,上代码吧!

先来看看这个装饰器:

public abstract class GreetingDecorator implements Greeting {

    private Greeting greeting;

    public GreetingDecorator(Greeting greeting) {
        this.greeting = greeting;
    }

    @Override
    public void sayHello(String name) {
        greeting.sayHello(name);
    }
}

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

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

第一个具体装饰器 GreetingBefore:

public class GreetingBefore extends GreetingDecorator {

    public GreetingBefore(Greeting greeting) {
        super(greeting);
    }

    @Override
    public void sayHello(String name) {
        before();
        super.sayHello(name);
    }

    private void before() {
        System.out.println("Before");
    }
}

第二个具体装饰器 GreetingAfter:

public class GreetingAfter extends GreetingDecorator {

    public GreetingAfter(Greeting greeting) {
        super(greeting);
    }

    @Override
    public void sayHello(String name) {
        super.sayHello(name);
        after();
    }

    private void after() {
        System.out.println("After");
    }
}

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

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

public class ClientDecorator {

    public static void main(String[] args) {
        Greeting greeting = new GreetingAfter(new GreetingBefore(new GreetingImpl()));
        greeting.sayHello("Jack");
    }
}

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

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

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

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

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

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

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

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

如果本文对您有帮助,那就顶起来吧!

© 著作权归作者所有

共有 人打赏支持
黄勇

黄勇

粉丝 6216
博文 121
码字总数 216155
作品 1
浦东
CTO(技术副总裁)
加载中

评论(35)

n
nealke
好文章!
黄勇
黄勇

引用来自“Xsank”的评论

感谢分享,收获良多~
感谢您的关注!今天我的最新动态里被您刷屏了
Xsank
Xsank
感谢分享,收获良多~
Xsank
Xsank

引用来自“黄开源中国”的评论

引用来自“黄勇”的评论

看来过节确实人气不怎么样啊!这么逆天的文章竟然没人顶?

手工给你顶一个。。最近一直在看你跟另外一个oscer的文章。。获益匪浅。感谢~!
另外一个是谁?
fhr91
fhr91
好文章!nice
曾经的十字镐
曾经的十字镐
勇哥写的太经典了1使我受益匪浅呀13
TimZhou
TimZhou
看大神讲解的这个两个模式想忘掉都难啊,什么时候把剩余的其他二十几种设计模式也讲解一下啊。让我们膜拜膜拜!
zmf
zmf
自从勇哥会了百度离线在线播放,博文越来越风骚越来越够劲越来越淫荡,给力,我喜欢
黄勇
黄勇

引用来自“叫悟空的猴子”的评论

看了楼主的文章总是忍不住留言。就是为了鼓励楼主这么有趣的文章多写一些。

你的鼓励我收到了,多谢!我会继续努力的。
叫悟空的猴子
叫悟空的猴子
看了楼主的文章总是忍不住留言。就是为了鼓励楼主这么有趣的文章多写一些。
Spring都用到了那些设计模式(2)

上一节我们学习了Spring中两个工厂模式,接下来我们继续学习Spring中的其他典型模式。 3. 单例(Singleton) 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 Spring中的单例模式完...

博为峰教研组
2016/11/18
7
0
java 设计模式 (代理模式和装饰者模式的区别)

废话不多说,直接上代码: public interface Sourceable { public void method(); } public class Source implements Sourceable { @Override public void method() { System.out.println("I......

大胖和二胖
2016/11/28
255
0
《设计模式——可复用面向对象软件的基础》学习笔记(1.5)Organizing the catalog

1.Classify DPs by purpose. (1)Creational DP: the process of object creation. Factory Method, Abstract Factory, Builder, Prototype, Singleton (2)Structural DP: composition of cla......

晨曦之光
2012/04/24
63
0
23种设计模式总结(二)

适配器模式,包括3种,针对类的适配器、针对对象的适配器、针对接口的适配器,其中针对对象的适配器是关键因为它可以理解为其他结构性模式的基础。下面分别进行说明 类的适配器 要点:3个主要...

大胖和二胖
2016/11/29
10
0
Design Pattern - Proxy

Proxy模式真心是一个比较精巧的模式, 尤其通过C++重载运算符(& ->),让你瞬间对C++的感情重归于好,忘记它的库或者框架的缺乏之痛苦.(另外一个令其他高级语言目瞪的是重载new/delete,来管理大块...

woodo
2014/04/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

MySQL 乱七八糟的可重复读隔离级别实现

MySQL 乱七八糟的可重复读隔离级别实现 摘要: 原文可阅读 http://www.iocoder.cn/Fight/MySQL-messy-implementation-of-repeatable-read-isolation-levels 「shimohq」欢迎转载,保留摘要,谢...

DemonsI
58分钟前
2
0
Spring源码阅读——2

在阅读源码之前,先了解下Spring的整体架构: 1、Spring的整体架构 1. Ioc(控制反转) Spring核心模块实现了Ioc的功能,它将类与类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描...

叶枫啦啦
今天
1
0
jQuery.post() 函数格式详解

jquery的Post方法$.post() $.post是jquery自带的一个方法,使用前需要引入jquery.js 语法:$.post(url,data,callback,type); url(必须):发送请求的地址,String类型 data(可选):发送给后台的...

森火
今天
0
0
referer是什么意思?

看看下面这个回答(打不开网页可以把网址复制到搜索栏): https://zhidao.baidu.com/question/577842068.html

杉下
今天
1
0
使用U盘安装CentOS-解决U盘找不到源

1. 使用UltraISO制作CentOS安装盘 如果需要安装带界面的系统,为保证安装顺利,可选择Everything版本的ISO制作安装盘。 2. 在BIOS中选择使用U盘安装 系统启动后,进入安装选择界面,其中有三...

Houor
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部