装饰模式(Decorator)

1. 模式的定义

如何实现灵活的奖金计算?假设奖金的计算体系如下:

  • 每个人当月业务奖金:当月销售额 * 3%
  • 每个人累计奖金:总的回款额 * 0.1%
  • 团队奖金:团队总销售额 * 1%

奖金计算面临的问题: 1. 计算逻辑复杂 2. 要有灵活性,可以方便地增加或者减少功能 3. 动态组合计算,不同的人参与的计算不同

抽象出来的问题:如何才能透明地给一个对象增加功能,并实现功能的动态组合?

装饰模式的定义: 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。

2. UML图

Component:组件对象的接口,可以给这些对象动态地添加职责

ConcreteComponent:具体的组件对象,实现组件接口,通常是被装饰器装饰的原始对象,也就是可以给这个对象添加职责

Decorator:所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,并持有一个Component对象,其实就是持有一个被装饰的对象

ConcreteDecorator:具体的装饰器对象,实现具体要向被装饰对象添加的功能

代码:

/** * 在内存中模拟数据库,准备点测试数据,好计算奖金 */public class TempDB { private TempDB(){} /** * 记录每个人的月度销售额,只用了人员,月份没有用 */ public static Map<String,Double> mapMonthSaleMoney = new HashMap<String,Double>(); static{ //填充测试数据 mapMonthSaleMoney.put("张三",10000.0); mapMonthSaleMoney.put("李四",20000.0); mapMonthSaleMoney.put("王五",30000.0); } }/** * 计算奖金的组件接口 */public abstract class Component { /** * 计算某人在某段时间内的奖金,有些参数在演示中并不会使用, * 但是在实际业务实现上是会用的,为了表示这是个具体的业务方法, * 因此这些参数被保留了 * @param user 被计算奖金的人员 * @param begin 计算奖金的开始时间 * @param end 计算奖金的结束时间 * @return 某人在某段时间内的奖金 */ public abstract double calcPrize(String user,Date begin,Date end); }/** * 基本的实现计算奖金的类,也是被装饰器装饰的对象 */public class ConcreteComponent extends Component{ public double calcPrize(String user, Date begin, Date end) { //只是一个默认的实现,默认没有奖金 return 0; } }/** * 装饰器的接口,需要跟被装饰的对象实现同样的接口 */public abstract class Decorator extends Component{ /** * 持有被装饰的组件对象 */ protected Component c; /** * 通过构造方法传入被装饰的对象 * @param c被装饰的对象 */ public Decorator(Component c){ this.c = c; } public double calcPrize(String user, Date begin, Date end) { //转调组件对象的方法 return c.calcPrize(user, begin, end); } }/** * 装饰器对象,计算当月业务奖金 */public class MonthPrizeDecorator extends Decorator{ public MonthPrizeDecorator(Component c){ super(c); } public double calcPrize(String user, Date begin, Date end) { //1:先获取前面运算出来的奖金 double money = super.calcPrize(user, begin, end); //2:然后计算当月业务奖金,按照人员和时间去获取当月的业务额,然后再乘以3% double prize = TempDB.mapMonthSaleMoney.get(user) * 0.03; System.out.println(user+"当月业务奖金"+prize); return money + prize; } }/** * 装饰器对象,计算累计奖金 */public class SumPrizeDecorator extends Decorator{ public SumPrizeDecorator(Component c){ super(c); } public double calcPrize(String user, Date begin, Date end) { //1:先获取前面运算出来的奖金 double money = super.calcPrize(user, begin, end); //2:然后计算累计奖金,其实这里应该按照人员去获取累计的业务额,然后再乘以0.1% //简单演示一下,假定大家的累计业务额都是1000000元 double prize = 1000000 * 0.001; System.out.println(user+"累计奖金"+prize); return money + prize; } }/** * 装饰器对象,计算当月团队业务奖金 */public class GroupPrizeDecorator extends Decorator{ public GroupPrizeDecorator(Component c){ super(c); } public double calcPrize(String user, Date begin, Date end) { //1:先获取前面运算出来的奖金 double money = super.calcPrize(user, begin, end); //2:然后计算当月团队业务奖金,先计算出团队总的业务额,然后再乘以1% //假设都是一个团队的 double group = 0.0; for(double d : TempDB.mapMonthSaleMoney.values()){ group += d; } double prize = group * 0.01; System.out.println(user+"当月团队业务奖金"+prize); return money + prize; } }/** * 使用装饰模式的客户端 */public class Client { public static void main(String[] args) { //先创建计算基本奖金的类,这也是被装饰的对象 Component c1 = new ConcreteComponent(); //然后对计算的基本奖金进行装饰,这里要组合各个装饰 //说明,各个装饰者之间最好是不要有先后顺序的限制,也就是先装饰谁和后装饰谁都应该是一样的 //一层一层叠加的功能 //先组合普通业务人员的奖金计算 Decorator d1 = new MonthPrizeDecorator(c1); Decorator d2 = new SumPrizeDecorator(d1); //注意:这里只需要使用最后组合好的对象调用业务方法即可,会依次调用回去 //日期对象都没有用上,所以传null就可以了 double zs = d2.calcPrize("张三",null,null); System.out.println("==========张三应得奖金:"+zs); double ls = d2.calcPrize("李四",null,null); System.out.println("==========李四应得奖金:"+ls); //如果是业务经理,还需要一个计算团队的奖金计算 Decorator d3 = new GroupPrizeDecorator(d2); double ww = d3.calcPrize("王五",null,null); System.out.println("==========王经理应得奖金:"+ww); } }

3. 研磨设计模式

1)装饰模式的功能:实现动态地为对象添加功能,一层一层的包装

2)类功能的扩展:1. 继承 2.对象的组合

3)Java中的装饰模式:IO流

  • InputStream相当于Component
  • FileInputStream,ObjectInputStream,StringBufferInputStream相当于ConcreteComponent
  • FilterInputStream相当于Decorator
  • DataInputStream,BufferedInputStream相当于ConcreteDecorator

4)装饰器模式与AOP AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点植入到面向对象的软件系统中,从而实现了横切关注点的模块化。

AOP能够将那些与业务无关,却被业务模块所共同调用的逻辑,比如,事务处理,日志管理,权限控制,封装起来,便于减少系统的重复代码,降低模块间的耦合度,利于未来的维护和操作。 AOP一个更重要的变化是思想上的变化(主从换位),让原本主动调用的功能模块变成了被动等待。

/** * 商品销售管理的业务接口 相当于Component */public interface GoodsSaleEbi { /** * 保存销售信息,本来销售数据应该是多条,太麻烦了,为了演示,简单点 * @param user 操作人员 * @param customer 客户 * @param saleModel 销售数据 * @return 是否保存成功 */ public boolean sale(String user,String customer,SaleModel saleModel); }/** * 封装销售单的数据,简单的示意一些 */public class SaleModel { /** * 销售的商品 */ private String goods; /** * 销售的数量 */ private int saleNum; public String getGoods() { return goods; } public void setGoods(String goods) { this.goods = goods; } public int getSaleNum() { return saleNum; } public void setSaleNum(int saleNum) { this.saleNum = saleNum; } public String toString(){ return "商品名称="+goods+",购买数量="+saleNum; } }/** * 业务对象 */public class GoodsSaleEbo implements GoodsSaleEbi{ public boolean sale(String user,String customer, SaleModel saleModel) { System.out.println(user+"保存了"+customer+"购买 "+saleModel+" 的销售数据"); return true; } }/** * 装饰器的接口,需要跟被装饰的对象实现同样的接口 */public abstract class Decorator implements GoodsSaleEbi{ /** * 持有被装饰的组件对象 */ protected GoodsSaleEbi ebi; /** * 通过构造方法传入被装饰的对象 * @param ebi被装饰的对象 */ public Decorator(GoodsSaleEbi ebi){ this.ebi = ebi; } }/** * 实现权限控制 */public class CheckDecorator extends Decorator{ public CheckDecorator(GoodsSaleEbi ebi){ super(ebi); } public boolean sale(String user,String customer, SaleModel saleModel) { //简单点,只让张三执行这个功能 if(!"张三".equals(user)){ System.out.println("对不起"+user+",你没有保存销售单的权限"); //就不再调用被装饰对象的功能了 return false; }else{ return this.ebi.sale(user, customer, saleModel); } } }/** * 实现日志记录 */public class LogDecorator extends Decorator{ public LogDecorator(GoodsSaleEbi ebi){ super(ebi); } public boolean sale(String user,String customer, SaleModel saleModel) { //执行业务功能 boolean f = this.ebi.sale(user, customer, saleModel); //在执行业务功能过后,记录日志 DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); System.out.println("日志记录:"+user+"于"+df.format(new Date())+"时保存了一条销售记录,客户是"+customer+",购买记录是"+saleModel); return f; } }public class Client { public static void main(String[] args) { //得到业务接口,组合装饰器 GoodsSaleEbi ebi = new CheckDecorator(new LogDecorator(new GoodsSaleEbo())); //准备测试数据 SaleModel saleModel = new SaleModel(); saleModel.setGoods("Moto手机"); saleModel.setSaleNum(2); //调用业务功能 ebi.sale("张三","张三丰", saleModel); ebi.sale("李四","张三丰", saleModel); } }

5)装饰器模式的本质:动态组合

文章转自:https://blog.csdn.net/jiangxishidayuan

原文发布于微信公众号 - JAVA高级架构(gaojijiagou)

原文发表时间:2018-04-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android 开发者

[译] Kotlin 揭秘:理解并速记 Lambda 语法

在奥地利旅行期间,我参观了维也纳的奥地利国家图书馆。特别是国会大厅,这个令人惊叹的空间感觉就像印第安纳琼斯电影中的一些东西。房间周围的空间是这些门被装在架子上,...

9700
来自专栏小樱的经验随笔

设计模式六大原则(5):迪米特法则

定义:一个对象应该对其他对象保持最少的了解。 问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。 解决方案:尽量降低类与...

34060
来自专栏阿杜的世界

《重构》阅读笔记-代码的坏味道

开发者必须通过实践培养自己的经验和直觉,培养出自己的判断力:学会判断一个类内有多少个实例变量算是太大、学会判断一个函数内有多少行代码才算太长。

7820
来自专栏PHP在线

编程语言中的闭包

首先,我觉得,一个概念,如果不理解也不影响使用的话,那么,就没必要去理解它、去学习它。闭包就是这样一个概念,你不理解它也能很好的用它。俺这两年写as3程序,是天...

38640
来自专栏PPV课数据科学社区

【学习】《R实战》读书笔记(第四章)

读书会是一种在于拓展视野、宏观思维、知识交流、提升生活的活动。PPV课R语言读书会以“学习、分享、进步”为宗旨,通过成员协作完成R语言专业书籍的精读和分享,达到...

29950
来自专栏Phoenix的Android之旅

重构-为什么 if-else 不是好代码

平时开发中if-else用的多吗? 其实这是个再正常不过的coding习惯,当我们代码量小的时候用来做条件判断是再简单不过的了。 但对于优秀程序员来说,这并不是...

18610
来自专栏mathor

HDOJ1257最少拦截系统

 这个题一开始我百思不得其解,后来看了一眼别人的题解标题,最长上升子序列?我当时还纳闷,这和最长上升子序列有什么关系,后来仔细一想还确实是。因为导弹拦截系统...

10420
来自专栏Java大联盟

23种设计模式详解(三)

10510
来自专栏写代码的海盗

SEO是件贼有意思的事情 golang入坑系列

这两天迷上了SEO。真心看不起百度的竞价排名,但作为一个商业网站,赚钱是一件无可厚非的事情。只做活雷锋,没有大金主是做不长的。做完功课后,发现百度和google...

32650
来自专栏即时通讯技术

字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8

原作者:阮一峰(ruanyifeng.com),现重新整理发布,感谢原作者的无私分享。

26120

扫码关注云+社区

领取腾讯云代金券