Proxy 遇上 Decorator
博客专区 > 黄勇 的博客 > 博客详情
Proxy 遇上 Decorator
黄勇 发表于4年前
Proxy 遇上 Decorator
  • 发表于 4年前
  • 阅读 2446
  • 收藏 84
  • 点赞 19
  • 评论 35

标题:腾讯云 新注册用户域名抢购1元起>>>   

有些朋友们看过《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 模式吧!

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

共有 人打赏支持
黄勇
粉丝 5695
博文 114
码字总数 196279
作品 1
评论 (35)
古月楼
中秋都不休息。都在写文章啊。不错。
优游幻世
学习了,学过decorator模式,但没见过现成的例子,原来IO包就是这种模式的应用。
黄勇
看来过节确实人气不怎么样啊!这么逆天的文章竟然没人顶?
huWasabi
好文,偶也要牛逼起来~
黄开源中国

引用来自“黄勇”的评论

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

手工给你顶一个。。最近一直在看你跟另外一个oscer的文章。。获益匪浅。感谢~!
稻草鸟人
好文,好淫
Rezeroer
代理模式(Proxy 模式)可理解为:我想做,但不直接做,我需要有一个代理把东西拿过来做

装饰模式(Decorator 模式)可理解为:我想做,但不直接做,我需要装潢装潢去做
1007890824
好文!mark下了
didxga
文字生动,条理清晰,很好。
RyanHoo
好文章,简洁明了,鞭辟入里,这么风骚入骨的文风跟本瑞有的一拼啊~哈哈哈哈,顶起!
我要吃螃蟹
学习了 赞个
NO17
看了这篇文章,一个字爽,从头爽到尾!
RyanHoo
问下这个UML拿什么画的,显然不是rose,似乎是Power Designer吧?Mac上有木有推荐的UML Tool?
黄勇

引用来自“RyanHoo”的评论

问下这个UML拿什么画的,显然不是rose,似乎是Power Designer吧?Mac上有木有推荐的UML Tool?

我用 PowerDesigner 画的,至今我还没用过 Mac,哎!
aqia358
相当专业呀,学习啦
一条大河波浪宽
请问楼主的类图是用什么画的啊?自己画的还是能自动生成啊?
一条大河波浪宽
写的真好,透彻明了!
黄勇

引用来自“雅典娜拉”的评论

请问楼主的类图是用什么画的啊?自己画的还是能自动生成啊?

我用 PowerDesigner 画的
一条大河波浪宽

引用来自“黄勇”的评论

引用来自“雅典娜拉”的评论

请问楼主的类图是用什么画的啊?自己画的还是能自动生成啊?

我用 PowerDesigner 画的

哦,知道啦,明天我做汇报,,,也用一下~~多谢
shu
×
黄勇
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: