文档章节

研磨设计模式之 装饰模式-2

We911
 We911
发布于 2017/02/08 10:13
字数 3962
阅读 0
收藏 0
点赞 0
评论 0

研磨设计模式之 装饰模式-2

2  解决方案

2.1  装饰模式来解决

        用来解决上述问题的一个合理的解决方案,就是使用装饰模式。那么什么是装饰模式呢?
(1)装饰模式定义



 (2)应用装饰模式来解决的思路
        虽然经过简化,业务简单了很多,但是需要解决的问题不会少,还是要解决:要透明的给一个对象增加功能,并实现功能的动态组合。
        所谓透明的给一个对象增加功能,换句话说就是要给一个对象增加功能,但是不能让这个对象知道,也就是不能去改动这个对象。而实现了能够给一个对象透明的增加功能,自然就能够实现功能的动态组合,比如原来的对象有A功能,现在透明的给它增加了一个B功能,是不是就相当于动态组合了A和B功能呢。
        要想实现透明的给一个对象增加功能,也就是要扩展对象的功能了,使用继承啊,有人马上提出了一个方案,但很快就被否决了,那要减少或者修改功能呢?事实上继承是非常不灵活的复用方式。那就用“对象组合”嘛,又有人提出新的方案来了,这个方案得到了大家的赞同。
        在装饰模式的实现中,为了能够和原来使用被装饰对象的代码实现无缝结合,是通过定义一个抽象类,让这个类实现与被装饰对象相同的接口,然后在具体实现类里面,转调被装饰的对象,在转调的前后添加新的功能,这就实现了给被装饰对象增加功能,这个思路跟“对象组合”非常类似。如果对“对象组合”不熟悉,请参见3.1的第2小节。
在转调的时候,如果觉得被装饰的对象的功能不再需要了,还可以直接替换掉,也就是不再转调,而是在装饰对象里面完全全新的实现。


2.2  模式结构和说明

 装饰模式的结构如图1所示:

 

 

                                                图1  装饰模式结构图
Component:
        组件对象的接口,可以给这些对象动态的添加职责。
ConcreteComponent:
        具体的组件对象,实现组件对象接口,通常就是被装饰器装饰的原始对象,也就是可以给这个对象添加职责。
Decorator:
        所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,并持有一个Component对象,其实就是持有一个被装饰的对象。
        注意这个被装饰的对象不一定是最原始的那个对象了,也可能是被其它装饰器装饰过后的对象,反正都是实现的同一个接口,也就是同一类型。
ConcreteDecorator:
        实际的装饰器对象,实现具体要向被装饰对象添加的功能。

2.3  装饰模式示例代码

(1)先来看看组件对象的接口定义,示例代码如下:

?
1
2
3
4
5
6
7
8
9
/**
  * 组件对象的接口,可以给这些对象动态的添加职责
  */
public  abstract  class  Component {
     /**
      * 示例方法
      */
     public  abstract  void  operation();
}

 (2)定义了接口,那就看看具体组件实现对象示意吧,示例代码如下:

?
1
2
3
4
5
6
7
8
/**
  * 具体实现组件对象接口的对象
  */
public  class  ConcreteComponent extends  Component {
     public  void  operation() {
         //相应的功能处理
     }
}

 (3)接下来看看抽象的装饰器对象,示例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
  * 装饰器接口,维持一个指向组件对象的接口对象,并定义一个与组件接口一致的接口
  */
public  abstract   class  Decorator extends  Component {
     /**
      * 持有组件对象
      */
     protected  Component component;
     /**
      * 构造方法,传入组件对象
      * @param component 组件对象
      */
     public  Decorator(Component component) {
         this .component = component;
     }
     public  void  operation() {
         //转发请求给组件对象,可以在转发前后执行一些附加动作
         component.operation();
     }
}

 (4)该来看看具体的装饰器实现对象了,这里有两个示意对象,一个示意了添加状态,一个示意了添加职责。先看添加了状态的示意对象吧,示例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
  * 装饰器的具体实现对象,向组件对象添加职责
  */
public  class  ConcreteDecoratorA extends  Decorator {
     public  ConcreteDecoratorA(Component component) {
         super (component);
     }
     /**
      * 添加的状态
      */
     private  String addedState; 
     public  String getAddedState() {
         return  addedState;
     }
     public  void  setAddedState(String addedState) {
         this .addedState = addedState;
     }
     public  void  operation() {
         //调用父类的方法,可以在调用前后执行一些附加动作
         //在这里进行处理的时候,可以使用添加的状态
         super .operation();
     }
}

 接下来看看添加职责的示意对象,示例代码如下:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
  * 装饰器的具体实现对象,向组件对象添加职责
  */
public  class  ConcreteDecoratorB extends  Decorator {
     public  ConcreteDecoratorB(Component component) {
         super (component);
     }
     /**
      * 需要添加的职责
      */
     private  void  addedBehavior() {
         //需要添加的职责实现
     }
     public  void  operation() {
         //调用父类的方法,可以在调用前后执行一些附加动作
         super .operation();
         addedBehavior();
     }
}

 

 

2.4  使用装饰模式重写示例

        看完了装饰模式的基本知识,该来考虑如何使用装饰模式重写前面的示例了。要使用装饰模式来重写前面的示例,大致会有如下改变:

  • 首先需要定义一个组件对象的接口,在这个接口里面定义计算奖金的业务方法,因为外部就是使用这个接口来操作装饰模式构成的对象结构中的对象
  • 需要添加一个基本的实现组件接口的对象,可以让它返回奖金为0就可以了
  • 把各个计算奖金的规则当作装饰器对象,需要为它们定义一个统一的抽象的装饰器对象,好约束各个具体的装饰器的接口
  • 把各个计算奖金的规则实现成为具体的装饰器对象

先看看现在示例的整体结构,好整体理解和把握示例,如图2所示:

 

 

                           图2  使用装饰模式重写示例的程序结构示意图
(1)计算奖金的组件接口和基本的实现对象
        在计算奖金的组件接口中,需要定义原本的业务方法,也就是实现奖金计算的方法,示例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
  * 计算奖金的组件接口
  */
public  abstract  class  Component {
     /**
      * 计算某人在某段时间内的奖金,有些参数在演示中并不会使用,
      * 但是在实际业务实现上是会用的,为了表示这是个具体的业务方法,
      * 因此这些参数被保留了
      * @param user 被计算奖金的人员
      * @param begin 计算奖金的开始时间
      * @param end 计算奖金的结束时间
      * @return 某人在某段时间内的奖金
      */
     public  abstract  double  calcPrize(String user
,Date begin,Date end);
}

  为这个业务接口提供一个基本的实现,示例代码如下:

?
1
2
3
4
5
6
7
8
9
/**
  * 基本的实现计算奖金的类,也是被装饰器装饰的对象
  */
public  class  ConcreteComponent extends  Component{
     public  double  calcPrize(String user, Date begin, Date end) {
         //只是一个默认的实现,默认没有奖金
         return  0 ;
     }
}

 (2)定义抽象的装饰器
在进一步定义装饰器之前,先定义出各个装饰器公共的父类,在这里定义所有装饰器对象需要实现的方法。这个父类应该实现组件的接口,这样才能保证装饰后的对象仍然可以继续被装饰。示例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
  * 装饰器的接口,需要跟被装饰的对象实现同样的接口
  */
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);
     }
}

(3)定义一系列的装饰器对象
       用一个具体的装饰器对象,来实现一条计算奖金的规则,现在有三条计算奖金的规则,那就对应有三个装饰器对象来实现,依次来看看它们的实现。
        这些装饰器涉及到的TempDB跟以前一样,这里就不去赘述了。
        首先来看实现计算当月业务奖金的装饰器,示例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
  * 装饰器对象,计算当月业务奖金
  */
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;
     }
}

 接下来看实现计算累计奖金的装饰器,示例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
  * 装饰器对象,计算累计奖金
  */
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;
     }
}

 接下来看实现计算当月团队业务奖金的装饰器,示例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
  * 装饰器对象,计算当月团队业务奖金
  */
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;
     }
}

 (4)使用装饰器的客户端
         使用装饰器的客户端,首先需要创建被装饰的对象,然后创建需要的装饰对象,接下来重要的工作就是组合装饰对象,依次对前面的对象进行装饰。
         有很多类似的例子,比如生活中的装修,就拿装饰墙壁来说吧:没有装饰前是原始的砖墙,这就好比是被装饰的对象,首先需要刷腻子,把墙找平,这就好比对原始的砖墙进行了一次装饰,而刷的腻子就好比是一个装饰器对象;好了,装饰一回了,接下来该刷墙面漆了,这又好比装饰了一回,刷的墙面漆就好比是又一个装饰器对象,而且这回被装饰的对象不是原始的砖墙了,而是被腻子装饰器装饰过后的墙面,也就是说后面的装饰器是在前面的装饰器装饰过后的基础之上,继续装饰的,类似于一层一层叠加的功能。
         同样的道理,计算奖金也是这样,先创建基本的奖金对象,然后组合需要计算的奖金类型,依次组合计算,最后的结果就是总的奖金。示例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
  * 使用装饰模式的客户端
  */
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);
     }
}

 测试一下,看看结果,示例如下:

?
1
2
3
4
5
6
7
8
9
10
张三当月业务奖金 300.0
张三累计奖金 1000.0
==========张三应得奖金: 1300.0
李四当月业务奖金 600.0
李四累计奖金 1000.0
==========李四应得奖金: 1600.0
王五当月业务奖金 900.0
王五累计奖金 1000.0
王五当月团队业务奖金 600.0
==========王经理应得奖金: 2500.0

      

      当测试运行的时候会按照装饰器的组合顺序,依次调用相应的装饰器来执行业务功能,是一个递归的调用方法,以业务经理“王五”的奖金计算做例子,画个图来说明奖金的计算过程吧,看看是如何调用的,如图3所示:


                                图3  装饰模式示例的组合和调用过程示意图
        这个图很好的揭示了装饰模式的组合和调用过程,请仔细体会一下。

 

         如同上面的示例,对于基本的计算奖金的对象而言,由于计算奖金的逻辑太过于复杂,而且需要在不同的情况下进行不同的运算,为了灵活性,把多种计算奖金的方式分散到不同的装饰器对象里面,采用动态组合的方式,来给基本的计算奖金的对象增添计算奖金的功能,每个装饰器相当于计算奖金的一个部分。
       这种方式明显比为基本的计算奖金的对象增加子类来得更灵活,因为装饰模式的起源点是采用对象组合的方式,然后在组合的时候顺便增加些功能。为了达到一层一层组装的效果,装饰模式还要求装饰器要实现与被装饰对象相同的业务接口,这样才能以同一种方式依次组合下去。
       灵活性还体现在动态上,如果是继承的方式,那么所有的类实例都有这个功能了,而采用装饰模式,可以动态的为某几个对象实例添加功能,而不是对整个类添加功能。比如上面示例中,客户端测试的时候,对张三李四就只是组合了两个功能,对王五就组合了三个功能,但是原始的计算奖金的类都是一样的,只是动态的为它增加的功能不同而已。

 

 

未完待续

本文转载自:http://blog.csdn.net/liduanw/article/details/8192908

共有 人打赏支持
We911
粉丝 0
博文 63
码字总数 0
作品 0
深圳
程序员
设计模式 2014-12-19

book: 阎宏《JAVA与模式》 架构设计栏目 http://blog.csdn.net/enterprise/column.html 概要: http://bbs.csdn.net/forums/Embeddeddriver 23种设计模式分别是: 1.单例模式 2.工厂方法模式...

jayronwang
2014/12/19
0
0
JavaScript常用设计模式

设计模式 设计模式是一种在长时间的经验与错误中总结出来可服用的解决方案。 设计模式主要分为3类: 创建型设计模式:专注于处理对象的创建 Constructor构造器模式,Factory工厂模式,Singl...

a独家记忆
07/13
0
0
我的Java设计模式-代理模式

写完上一篇之后有小伙伴问我有没有写过代理模式,想看看我的理解。原本我的设计模式系列是按照创建型-行为型-结构型的顺序写下去的,既然小伙伴诚心诚意了,我就大发慈悲的穿插一篇代理模式。...

Jet啟思
2017/11/29
0
0
Java之23种设计模式解析(二)

B、结构模式(7 种) 我们接着讨论设计模式,上篇文章我讲完了 5 种创建型模式,这章开始,我将讲下 7 种结构型模式:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模...

wersdffg
2015/02/15
0
0
小菜学设计模式——设计模式总结之结构型

1、设计模式总结 设计模式总共23个,但是常用的不到10个,下面就把这23个设计模式进行整理归类,具体如下: 1)创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型...

learn_more
2015/07/06
0
0
设计模式笔录(二),设计模式有哪些

本人出道5年,学习、编程、再学习、再编程一路走过,只是在笔和纸留下些脚印,实感惭愧。现开始把自己学习到的心得,实践中的体会,一一贴在互联网上,大家互相学习、探讨,寻找一些技术朋友...

方旭
2011/03/31
0
0
23种JAVA设计模式(3)-结构模式

B、结构模式(7种) 我们接着讨论设计模式,上篇文章我讲完了5种创建型模式,这章开始,我将讲下7种结构型模式:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。...

Mr_蜗牛
2015/04/15
0
0
ES7 Decorator 装饰者模式

原作者:玄农 装饰模式 设计模式大家都有了解,网上有很多系列教程,比如 JS设计模式等等。 这里只分享 装饰者模式 以及在 如何使用 ES7 的 概念 装饰模式 v.s. 适配器模式 装饰模式和适配器...

_朴灵_
05/14
0
0
你需要了解的23种JavaScript设计模式

为什么要学习设计模式? 在许多访谈中,你可能会遇到很多面向对象编程中的接口,抽象类,代理和以及其他与设计模式相关的问题。 一旦了解了设计模式,它会让你轻松应对任何访谈,并可以在你的...

java高级架构牛人
06/02
0
0
设计模式梳理(一)

设计模式梳理(一) 总体来说设计模式分为三大类: @案例源码地址:https://gitlab.com/lxqxsyu/DisgnPattern 创建型模式 简单工厂模式 工厂类是整个模式的关键。它包含必要的判断逻辑,能够...

lxq_xsyu
2017/11/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

expect脚本同步文件、expect脚本指定host和要同步的文件、构建文件分发系统

expect脚本同步文件 更改权限 执行脚本 查看执行结果 expect eof需要加上,作用是等脚本命令执行完再进行退出 expect脚本指定host和要同步的文件 更改权限,执行脚本 构建文件分发系统 需求背...

Zhouliang6
38分钟前
1
0
Hive应用:外部分区表

Hive应用:外部分区表 介绍 Hive可以创建外部分区表。创建表的时候,分区要在建表语句中体现。建完之后,你不会在表中看到数据,需要进行分区添加,使用alter语句进行添加。然后数据才会显示...

星汉
48分钟前
3
0
点击Enter登录

1. 效果 2. 实现过程(记得引入jq文件) //6.回车事件 登录 $(function() { document.onkeydown = function(event) { var e = event || window.event || arguments.callee.caller.arguments......

Lucky_Me
53分钟前
1
0
点击菜单内容切换

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .menu{ height: 38px; background-color: #eeeeee; line-height: 38px; } .mao{ ......

南桥北木
今天
1
0
OSChina 周六乱弹 —— 妹子和游戏哪个更好玩

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @andonny :分享唐朝乐队的单曲《国际歌》 《国际歌》- 唐朝乐队 手机党少年们想听歌,请使劲儿戳(这里) @举个栗子- :日常祈雨 邪恶的大祭...

小小编辑
今天
572
6
流利阅读笔记32-20180721待学习

“人工智能”造假:只有人工,没有智能 Lala 2018-07-21 1.今日导读 当今社会,擅长单个方面的人工智能已经盛行,手机借助 AI 智慧防抖技术帮助大家拍出清晰照片,谷歌研发的 AI 助手将可以帮...

aibinxiao
今天
8
0
我的成长记录(一)

今天突然精神抖擞,在我的博客下新开一项分类>成长记录,专门记录每隔一段时间我的一点感悟吧。因为今天才专门花时间新开这样一个分类,所以以前有过的一些感悟没有记录下来,现在已经想不起...

dtqq
今天
1
0
机器学习管理平台 MLFlow

最近工作很忙,博客一直都没有更新。抽时间给大家介绍一下Databrick开源的机器学习管理平台-MLFlow。 谈起Databrick,相信即使是不熟悉机器学习和大数据的工程湿们也都有所了解,它由Spark的...

naughty
今天
17
0
idea tomcat 远程调试

tomcat 配置 编辑文件${tomcat_home}/bin/catalina.sh,在文件开头添加如下代码。    CATALINA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=7829" Idea端配......

qwfys
今天
2
0
遍历目录下的文件每250M打包一个文件

#!/usr/bin/env python # -*- utf-8 -*- # @Time : 2018/7/20 0020 下午 10:16 # @Author : 陈元 # @Email : abcmeabc@163.com # @file : tarFile.py import os import tarfile import thr......

寻爱的小草
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部