在面向对象设计过程中,经常会遇到需要对现有的类的功能进行扩展,通常我们可以采用继承的方式。例如老罗最近在做手机,一开始需要定义手机所应具有的功能:

  1. interface Phone{
  2.  
  3. public void tel();
  4.  
  5. public void sms();
  6.  
  7. }

在此,为简单起见,只是定义了接打电话和收发短信功能。

然后,老罗开始造手机,经过两年艰苦努力,第一代手机T1终于面世了,很高兴的开了发布会,反响还不错。

  1. class T1 implements Phone{
  2.  
  3. @Override
  4. public void tel() {
  5. System.out.println("可以实现基本的接打电话");
  6. }
  7.  
  8. @Override
  9. public void sms() {
  10. System.out.println("可以实现基本的收发短信功能");
  11. }
  12.  
  13. }

T1面世了,当然,只有不短的追求才有更大的进步的,在进一步的努力后,新一代的T2可以在T1的基础上实现安装Android软件的功能了。通常情况下,我们可能进行如下定义:

  1. class T2 extends T1{
  2.  
  3. public void installApk(){
  4. System.out.println("可以安装Android软件了");
  5. }
  6.  
  7. }

显然,采取了继承的方式,T2在T1的基础上新增了自己所具有的更多功能——可以安装Android软件。这是一个进步。

又过了一段时间,老罗想出做手机不仅仅需要满足这些基本的功能,还应该最大程度上的最好用户体验,于是,目前最快的闪拍被想到了,此功能被加入到了最新一代的T3中。

  1. class T3 extends T2{
  2.  
  3. public void fastestPhoto(){
  4. System.out.println("可以实现目前最快的闪拍");
  5. }
  6.  
  7. }

自然的,我们首先想到的还是通过继承的方式,在继承第2代基础上扩充了T3的功能。

当然,这是一件相当值得庆贺的事情。现在T1、T2、T3都面向市场推出了。由于市场需求较大,每一代的手机都在不断的制造中。现在有一个新的需求出来了,电信的用户反馈,这么手机都不支持电信卡,怎么办?

于是,老罗为了满足电信用户,急需在T2上推出相应的电信手机,那么现在怎么办呢?第一个想到的可能是修改T2类:

  1. class T2 extends T1 {
  2.  
  3. public void installApk() {
  4. System.out.println("可以安装Android软件了");
  5. }
  6.  
  7. @Override
  8. public void tel() {
  9. supportDx();
  10. System.out.println("可以实现基本的接打电话");
  11. }
  12.  
  13. @Override
  14. public void sms() {
  15. supportDx();
  16. System.out.println("可以实现基本的收发短信功能");
  17. }
  18.  
  19. public void supportDx() {
  20. System.out.println("可以支持电信用户了");
  21. }
  22.  
  23. }

现在问题就出来了,由于继承关系的存在,虽然表面上只是修改了T2类,实际上其所有的子类都间接的被修改了。那么现在又有两个选择,要么修改其所有子类,这肯定不实际,要么不修改T2类,以免带来不必要的T2修改后对其所有子类的影响,但为了满足需求,可能得继承T2类新定义一个新的类,这样会导致类的无线膨胀问题(因为这种需求是不可预估的),那么有没有什么好的解决方案呢?

于是,基本包装/装饰而不是继承来实现此类场景的类的设计是一个不错的选择。由此带来的设计模式称之为"装饰模式"。

为了给一个现有的类增加一些新的功能,而不引其原来类的修改,用装饰模式去代替继承模式,要求装饰类和被装饰类实现同一接口,装饰对象有被装饰对象的实例。

那么我们具体看一下在上面场景中使用装饰模式如何设计类。

  1. class T2 implements Phone {
  2.  
  3. private Phone phone;
  4.  
  5. public T2(Phone phone) {
  6. this.phone = phone;
  7. }
  8.  
  9. public void installApk() {
  10. System.out.println("可以安装Android软件了");
  11. }
  12.  
  13. @Override
  14. public void tel() {
  15. phone.tel();
  16. }
  17.  
  18. @Override
  19. public void sms() {
  20. phone.tel();
  21. }
  22.  
  23. }

T3类设计与之类似,那么现在T2需要支持电信用户呢?这是直接修改T2类即可,并且对其他类(因为它没有子类之说了)是没有影响的。

  1. class T2 implements Phone {
  2.  
  3. private Phone phone;
  4.  
  5. public T2(Phone phone) {
  6. this.phone = phone;
  7. }
  8.  
  9. public void installApk() {
  10. System.out.println("可以安装Android软件了");
  11. }
  12.  
  13. @Override
  14. public void tel() {
  15. this.supportDx();
  16. phone.tel();
  17. }
  18.  
  19. @Override
  20. public void sms() {
  21. this.supportDx();
  22. phone.tel();
  23. }
  24.  
  25. public void supportDx() {
  26. System.out.println("可以支持电信用户了");
  27. }
  28.  
  29. }

测试:

  1. public class DecoratorTest {
  2.  
  3. public static void main(String[] args) {
  4. Phone t1 = new T1();
  5. Phone t2 = new T2(t1);
  6. t2.tel();
  7. t2.sms();
  8. }
  9.  
  10. }

怎么样,利用装饰模式设计类代替原本的继承是不是优势马上显示出来了,可能有人会说,这样设计会违背T2的愿意,可能有些T2本没必要支持电信用户,哦,确实如此,那好办啊,在创建的时候T2对象的时候加一个参数去控制就可以了啊。大概如下:

  1. class T2 implements Phone {
  2.  
  3. private Phone phone;
  4. private boolean isSupportDx;
  5.  
  6. public T2(Phone phone, boolean isSupportDx) {
  7. this.phone = phone;
  8. this.isSupportDx = isSupportDx;
  9. }
  10.  
  11. public void installApk() {
  12. System.out.println("可以安装Android软件了");
  13. }
  14.  
  15. @Override
  16. public void tel() {
  17. this.supportDx();
  18. phone.tel();
  19. }
  20.  
  21. @Override
  22. public void sms() {
  23. this.supportDx();
  24. phone.tel();
  25. }
  26.  
  27. public void supportDx() {
  28. if (isSupportDx) {
  29. System.out.println("可以支持电信用户了");
  30. }
  31. }
  32.  
  33. }
  1. public class DecoratorTest {
  2.  
  3. public static void main(String[] args) {
  4. Phone t1 = new T1();
  5. Phone t2 = new T2(t1, true); // Phone t2 = new T2(t1, false);
  6. t2.tel();
  7. t2.sms();
  8. }
  9.  
  10. }

怎么样?装饰模式还是不错的吧,Java IO中的包装流都是采用装饰模式实现的哦。。

设计模式总结篇系列:装饰器模式(Decorator)的更多相关文章

  1. 设计模式(八)装饰器模式Decorator(结构型)

    设计模式(八)装饰器模式Decorator(结构型) 1. 概述 若你从事过面向对象开发,实现给一个类或对象增加行为,使用继承机制,这是所有面向对象语言的一个基本特性.如果已经存在的一个类缺少某些方法 ...

  2. 【PHP设计模式 09_ZhuangShiQi.php】装饰器模式 (decorator)

    <?php /** * [装饰器模式 (decorator)] * 有时候发布一篇文章需要经过很多人手,层层处理 */ header("Content-type: text/html; ...

  3. 设计模式(三)——装饰器模式(Decorator Pattern)

    发现太过于刻意按照计划来写博客,有点不实际,刚好最近在一个网课上复习AOP的知识,讲到了装饰器模式和代理模式,顺便复习总结一下. 首先了解一下装饰器模式,从名字里面可以看出来,装饰器模式就类似于房子装 ...

  4. php设计模式课程---7、装饰器模式如何使用

    php设计模式课程---7.装饰器模式如何使用 一.总结 一句话总结: 装饰器的核心是获取了文章类整个类,而不是获取了文章内容,有了这个文章类,我想给你加多少装饰就给你加多少装饰(将文章这个类封装进去 ...

  5. 装饰器模式-Decorator(Java实现)

    装饰器模式-Decorator(Java实现) 装饰器模式允许向一个现有的对象添加新的功能, 同时又不改变其结构. 其中 "现有对象"在本文中是StringDisplay类. 添加 ...

  6. Java设计模式系列-装饰器模式

    原创文章,转载请标注出处:<Java设计模式系列-装饰器模式> 一.概述 装饰器模式作用是针对目标方法进行增强,提供新的功能或者额外的功能. 不同于适配器模式和桥接模式,装饰器模式涉及的是 ...

  7. 说说设计模式~装饰器模式(Decorator)~多功能消息组件的实现

    返回目录 为何要设计多功能消息组件 之前写过一篇装饰器模式的文章,感觉不够深入,这次的例子是实现项目中遇到的,所以把它拿出来,再写写,之前也写过消息组件的文章,主要采用了策略模式实现的,即每个项目可以 ...

  8. 装饰器模式 Decorator 结构型 设计模式 (十)

    引子           现实世界的装饰器模式 大家应该都吃过手抓饼,本文装饰器模式以手抓饼为模型展开简介 "老板,来一个手抓饼,  加个培根,  加个鸡蛋,多少钱?" 这句话会不 ...

  9. 设计模式学习心得<装饰器模式 Decorator>

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

  10. 23种设计模式之装饰器模式(Decorator Pattern)

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

随机推荐

  1. 新装云服务器没有iptables 文件,并且无法通过service iptables save操作

    在安装zookeeper时,需要放开端口2181 按照视频教程在 /etc/sysconfig/ 下对iptables 文件进行编辑,但是该目录下没有此文件 通过强行写iptables -P  OUT ...

  2. P2649 - 【NOIP2017】列队

    Description Sylvia 是一个热爱学习的女孩子. 前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia 所在的方阵中有 n×m 名学生,方阵的行数为 ...

  3. IOS开发中将定时器添加到runLoop中

    runLoop主要就是为线程而生的.他能够让线程在有任务的时候保持工作状态,没有任务的时候让线程处于休眠待备状态. 主线程的runloop默认是开启的.主线程上创建的定时器已经默认添加到runLoop ...

  4. laravel之url跳转

    1.先写一下路由 2.在写a标签跳转

  5. Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签

    写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...

  6. Linux shell编程— 命令替换

    有两种方法可以将命令输出赋给变量 反引号字符(`) $()格式 命令替换允许你将shell 命令的输出赋给变量 要么用一对反引号把整个命令行围起来: testing=`data` 要么使用$()格式 ...

  7. C语言复习5_调试

    使用CodeBlocks调试程序 首先要注意,只有打开projects(.cbp文件)的情况下才能debug,单独打开.c文件是不能debug的 1.在行号旁边左键,出现红点,表示为断点breakpo ...

  8. 俄罗斯方块(三):"流动"的方块

    问题的提出: 俄罗斯方块允许90度的坡,是不是有点不够科学#(滑稽) 想办法加一种会“滑坡”的方块 本文两大部分: 详细的描绘是怎样的“流动” 写代码,并整合进游戏 本文基于我写的 俄罗斯方块(一): ...

  9. Azure Web连接到Azure MySql Db

    这个问题折腾了好一会,简单记录一下. 两种方式: 输入"规则名称"."起始 IP"和"结束 IP",然后单击"保存". ...

  10. 依赖注入[7]: .NET Core DI框架[服务注册]

    包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.服务注册就是创建出现相应的ServiceDescriptor对象并将其添加到 ...