热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

设计模式(十)装饰者模式

一、定义装饰者模式是一种比较常见的模式,其定义如下:动态地给对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子

一、定义

装饰者模式是一种比较常见的模式,其定义如下:

动态地给对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更为灵活。如果不用装饰者模式,我们想要扩展一个对象的功能,我们可能会采用继承该对象的方式,然后重写里面的方法来实现扩展原有功能,但当对象变化频繁的时候,这种子类会有很多,装饰者模式有效避免了这种创建大量子类的现象,动态地扩展对象的功能。

装饰者模式通用类图如下:


 二、角色分析

装饰者模式有四个角色需要说明:


  • Component抽象构件 

抽象构建类,可以是抽象类或者接口,定义了被装饰者类的一些抽象方法等,即最基本的功能。


  • ConcreteComponent具体构件

具体构建类,实现或者继承了抽象构建类,实现了具体的方法,真正被装饰的其实是它


  • Decorator装饰角色

一般是一个抽象类,实现接口或者抽象方法,它里面不一定有抽象的方法,在它的属性里面必然有一个private变量指向Component抽象构件。


  • ConcreteDecorator具体装饰角色

真正在这里扩展原始对象的功能,针对不同的具体构建类,可以定义多个具体装饰角色,例如ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类。


三、装饰者模式案例分析

下面我们以一个日常生活中常见的例子来说明装饰者模式的使用。

冬天快到了,很多小伙伴都会喜欢打火锅,打火锅肯定要有一个汤底,然后我们可能还会点各种各样的配菜,在增加配菜的同时,我们需要动态计算吃这一顿的总价钱。

接下来我们就使用装饰者模式来把上面这种场景进行抽象。

首先,UML类图如下:

 我们首先定义抽象构件角色,主要提供两个方法:获取价格的方法getPrice()、获取描述信息的方法getDesc(),代码如下:

/*** 抽象构件: 火锅类*/
public abstract class HotPotComponent {/*** 获取价格** @return*/protected abstract BigDecimal getPrice();/*** 获取描述信息** @return*/protected abstract String getDesc();
}

接下来定义一个具体构件角色:本例中就是火锅底料对象,真正装饰的也就是它。

/*** 具体构件类: 火锅底料*/
public class HotPotSeasoningComponent extends HotPotComponent {@Overrideprotected BigDecimal getPrice() {return BigDecimal.valueOf(20);}@Overrideprotected String getDesc() {return "火锅底料";}
}

接着,定义一个抽象装饰者类,它继承自抽象构件角色,内存持有一个指向抽象构件角色的引用:

/*** 抽象装饰者类*/
public abstract class AbstractDecorator extends HotPotComponent {private HotPotComponent hotPotComponent;public AbstractDecorator(HotPotComponent hotPotComponent) {this.hotPotComponent = hotPotComponent;}@Overrideprotected BigDecimal getPrice() {return hotPotComponent.getPrice().add(getSideDishPrice());}@Overrideprotected String getDesc() {return hotPotComponent.getDesc() + " + " + getSideDishDesc();}/*** 获取配菜的描述** @return*/protected abstract String getSideDishDesc();/*** 获取配菜的价格** @return*/protected abstract BigDecimal getSideDishPrice();}

可以看到, 除了实现抽象构件的抽象方法之外,抽象装饰者角色内存还定义了配菜的一些方法。并且重写了抽象构件的getPrice()方法和getDesc()方法,动态的计算所有配菜的总价格。

有了抽象装饰者角色,就需要定义具体的装饰者角色进行增强了:

/*** 具体装饰者角色: 肥牛*/
public class FatCattleDecorator extends AbstractDecorator {public FatCattleDecorator(HotPotComponent hotPotComponent) {super(hotPotComponent);}@Overrideprotected String getSideDishDesc() {return "肥牛";}@Overrideprotected BigDecimal getSideDishPrice() {return BigDecimal.valueOf(20);}
}/*** 具体装饰者角色: 小白菜*/
public class CabbageDecorator extends AbstractDecorator {public CabbageDecorator(HotPotComponent hotPotComponent) {super(hotPotComponent);}@Overrideprotected String getSideDishDesc() {return "小白菜";}@Overrideprotected BigDecimal getSideDishPrice() {return BigDecimal.valueOf(10);}}

最后,我们定义一个场景类进行测试:

public class Client {public static void main(String[] args) {//层层包装HotPotComponent hotPotComponent = new HotPotSeasoningComponent();//一份肥牛hotPotComponent = new FatCattleDecorator(hotPotComponent);//一份肥牛hotPotComponent = new FatCattleDecorator(hotPotComponent);//一份小白菜hotPotComponent = new CabbageDecorator(hotPotComponent);System.out.println(hotPotComponent.getDesc() + " = " + hotPotComponent.getPrice() + "元");}
}

运行结果:

火锅底料 + 肥牛 + 肥牛 + 小白菜 = 70元

可见,成功扩展原有对象的功能,这样我们以后如果需要扩展火锅底料的功能,只需要增加一个具体的装饰者角色即可实现扩展,而不需要改动原有的代码,这符合开闭原则。


四、装饰者应用分析

装饰者模式在我们的IO流中体现的最为显著,下面我们看一下IO流中InputStream相关的UML类图:

对应前面的通用类图应该很容易看出各个角色分别是谁:


  • OutputStream和InputStream就对应于抽象构件角色(Component);
  • FileInputStream和FileOutputStream就对应具体构件角色(ConcreteComponent);
  • FilterOutputStream和FilterInputStream就对应着抽象装饰角色(Decorator);
  • BufferedOutputStream,DataOutputStream等等就对应着具体装饰角色; 

下面是关键源码:

//抽象类,抽象构建角色
public abstract class InputStream implements Closeable {//省略...
}//具体构建角色,也是具体被装饰的类
public class FileInputStream extends InputStream{//省略...
}//装饰者基类,该类持有一个抽象构建角色InputStream的引用
public class FilterInputStream extends InputStream {/*** The input stream to be filtered.*/protected volatile InputStream in;//省略...
}//具体装饰者角色
public class BufferedInputStream extends FilterInputStream {//省略...
}

五、总结

装饰者模式的优点:


  • 装饰者类和被装饰者类互不影响,独立扩展,他们之间没有耦合关系;
  • 装饰者模式是频繁继承的一个替换解决方案,可以重复包装,包装之后返回的还是抽象构建角色;
  • 装饰者模式可以动态地扩展一个实现类的功能;

装饰者模式的缺点:


  • 多层装饰之后会使系统更加复杂,后期不太方便维护和扩展,所以不建议嵌套太多层装饰,尽量减少装饰类的数量,降低系统的复杂度。

装饰者模式的使用场景:


  • 需要动态扩展一个类的功能,或给一个类在增加附加功能;
  • 需要频繁使用继承才能扩展功能时可以考虑使用装饰者模式,可以减少类的创建;
  • 需要为一批的兄弟类进行改装或加装功能,当前是首选装饰者模式;

推荐阅读
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
    本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
author-avatar
过客松鼠_230
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有