本文有一些争议,谈论的是设计模式中的代理模式装饰器模式的区别,但笔者是非常赞同文章的观点的,有种豁然开朗的感觉。

以下是原文


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

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

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

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

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

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

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

public interface Greeting {void sayHello(String name);
}
public class GreetingImpl implements Greeting {@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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 模式相同),但却有两点不同:

在装饰器中不是组合实现类 GreetingImpl,而是组合它的接口 Greeting。
下面通过两个 Decorator 的实现类(也就是具体装饰器),来提供多种功能的扩展。
我们不再需要一个能人,而需要一个团队!

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

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

屁话少说,上代码吧!

先来看看这个装饰器:

public abstract class GreetingDecorator implements Greeting {private Greeting greeting;public GreetingDecorator(Greeting greeting) {this.greeting = greeting;}@Overridepublic void sayHello(String name) {greeting.sayHello(name);}
}

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

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

第一个具体装饰器 GreetingBefore:

public class GreetingBefore extends GreetingDecorator {public GreetingBefore(Greeting greeting) {super(greeting);}@Overridepublic void sayHello(String name) {before();super.sayHello(name);}private void before() {System.out.println("Before");}
}

第二个具体装饰器 GreetingAfter:

public class GreetingBefore extends GreetingDecorator {public GreetingBefore(Greeting greeting) {super(greeting);}@Overridepublic void sayHello(String name) {before();super.sayHello(name);}private void before() {System.out.println("Before");}
}

需要注意的是,在具体装饰器的构造方法中调用了父类的构造方法,也就是把 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 模式吧!


后文:争议的评论

有部分人认为,代理模式和装饰器模式的本质区别是从作用区分的:

装饰者模式是使用的调用者从外部传进来的被装饰对象,调用者只想让你把他给你的对象加强一下,装饰一下. 代理模式使用的是代理对象在自己的构造方法里面new的一个被代理类的对象,不是调用者传入的,调用者不知道你找了其他人,他也不关心这些事,只要你把事情做好就行了.

根据《设计模式之蝉》的描述,代理模式可浅分为强制代理和普通代理,文中应该描述的是强制代理,所以笔者不赞同评论中关于使用的说法。但作用上的区分是有的,代理更多在于控制或完成额外的功能,装饰器更多是对原本功能的加强和修饰。

代理模式与装饰器模式有何区别?相关推荐

  1. 设计模式之门面模式与装饰器模式详解和应用

    目录 1 门面模式定义 1.1 门面模式的应用场景 1.2 门面模式的通用写法 1.3 门面模式业务场景实例 1.4 门面模式在源码中的应用 1.5 门面模式的优缺点 2 装饰器模式 2.1 装饰器模 ...

  2. java模式之装饰器模式

    定义: 装饰器模式也叫作包装器模式,只在不改变原有对象的基础上,动态的给一个对象添加一些额外的职责.就增加功能来说,装饰器模式相比生成子类更为灵活,属于结构型设计模式 装饰器模式提供了比继承更有弹性的 ...

  3. java外观设计修改_Java设计模式之外观模式和装饰器模式的设计(精选)

    前言 本篇来学习下结构型模式的外观模式和装饰器模式. 外观模式 简介 外观模式隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口.这种类型的设计模式属于结构型模式,它向现有的系统添加一个接 ...

  4. Java设计模式之五 ----- 外观模式和装饰器模式

    前言 在上一篇中我们学习了结构型模式的适配器模式和桥接模式.本篇则来学习下结构型模式的外观模式和装饰器模式. 外观模式 简介 外观模式隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口.这 ...

  5. 适配器模式、代理模式、装饰器模式使用场景区别

    适配器模式 适配器模式是提供和原先接口不一样的接口,做一个二次转换,原来的接口还是可以继续使用的. 代理模式 原先的接口是不能访问的(无权使用),只能通过代理来去访问原先接口.代理过程中可能会对原有接 ...

  6. 【设计模式】【结构型模式】装饰器模式

    概念 定义 装饰模式指动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活. 这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰类,用来包 ...

  7. IOS设计模式之二(门面模式,装饰器模式)

    本文原文请见:http://www.raywenderlich.com/46988/ios-design-patterns. 由 @krq_tiger(http://weibo.com/xmuzyq) ...

  8. Java设计模式学习总结(11)——结构型模式之装饰器模式

    装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装.这种模式创建了一个装饰类,用来包装原 ...

  9. 桥接模式和装饰器模式

    桥接模式:将抽象和实现分离,使它们可以独立变化. 装饰器模式:在不改变现有对象结构的情况下,动态地给该对象增加一些职责.桥接模式桥接实现了两个不同的接口(抽象),作用也就与名字一样,只是为了桥接为另一 ...

最新文章

  1. 如何快速优雅的在CSDN输入公式?
  2. 简单配置jena在eclipse的开发环境
  3. ORACLE 实现 ID 自增
  4. 记录——《C Primer Plus (第五版)》第十一章编程练习第三题
  5. OpenGL基础1:最简单的OpenGL例子
  6. 计算机图形学完整笔记(五):二维图形变换
  7. java生成pdf表格_java生成pdf文件 --- Table
  8. matlab dll 反编译,libmx.dll
  9. 验证二叉树的前序序列化
  10. 深入浅出程序设计竞赛(基础篇)
  11. react 组件封装原则_React 组件封装
  12. 孙悟空的师父是谁? (ZT)
  13. php文章下一页,php获取文章上一页与下一页的方法_php技巧
  14. sd卡数据恢复源码android,SD卡数据恢复非常简单,想学的看过来!
  15. 云安全|云原生安全概述
  16. 阿里云服务器搭建Minecraft我的世界配置选择
  17. python 微信机器人教程_Python创建微信机器人(附赠Python视频教程)
  18. OSChina 周三乱弹 ——纪念Bob Taylor
  19. 【愚公系列】2022年10月 微信小程序-电商项目-小微商户支付后端功能实现(node版)
  20. k8s集群部署方式(kubeadm方式安装k8s)

热门文章

  1. VR混战已开始,看各大虚拟现实如何争春?
  2. 视频教程-清华-尹成老师-Python爬虫day23-Python
  3. mysql日志文件在哪_linux中mysql日志文件在哪里?
  4. 举个栗子~Alteryx 技巧(3):离线激活 Alteryx Designer
  5. Lazada卖家如何绑定收款方式,p卡注册流程
  6. 计算机学科导论读书报告,计算机科学导论读书报告
  7. [golang gin框架] 24.Gin 商城项目-redis讲解以及操作
  8. ABViewer布局检测器功能以及性能改进
  9. android拍照软件推荐,Android缤纷创意生活 6款拍照软件推荐
  10. 人工智能-数学基础-假设检验