文档章节

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

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

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

3.3  装饰模式和AOP

        装饰模式和AOP在思想上有共同之处。可能有些朋友还不太了解AOP,下面先简单介绍一下AOP的基础知识。
1:什么是AOP——面向方面编程
        AOP是一种编程范式,提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。
        在面向对象开发中,考虑系统的角度通常是纵向的,比如我们经常画出的如下的系统架构图,默认都是从上到下,上层依赖于下层,如图5所示:

                                          图5  系统架构图示例图
        而在每个模块内部呢?就拿大家都熟悉的三层架构来说,也是从上到下来考虑的,通常是表现层调用逻辑层,逻辑层调用数据层,如图6所示:


图6  三层架构示意图

        慢慢的,越来越多的人发现,在各个模块之中,存在一些共性的功能,比如日志管理、事务管理等等,如图7所示:
 

                                         图7  共性功能示意图

        这个时候,在思考这些共性功能的时候,是从横向在思考问题,与通常面向对象的纵向思考角度不同,很明显,需要有新的解决方案,这个时候AOP站出来了。
        AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化。
        AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
        AOP之所以强大,就是因为它能够自动把横切关注点的功能模块,自动织入回到软件系统中,这是什么意思呢?
        先看看没有AOP,在常规的面向对象系统中,对这种共性的功能如何处理,大都是把这些功能提炼出来,然后在需要用到的地方进行调用,只画调用通用日志的公共模块,其它的类似,就不去画了,如图8所示:


                    图8  调用公共功能示意图

        看清楚,是从应用模块中主动去调用公共模块,也就是应用模块要很清楚公共模块的功能,还有具体的调用方法才行,应用模块是依赖于公共模块的,是耦合的,这样一来,要想修改公共模块就会很困难了,牵一而发百。
        看看有了AOP会怎样,还是画个图来说明,如图9所示:


                 图9  AOP的调用示意图

        乍一看,跟上面不用AOP没有什么区别嘛,真的吗?看得仔细点,有一个非常非常大的改变,就是所有的箭头方向反过来了,原来是应用系统主动去调用各个公共模块的,现在变成了各个公共模块主动织入回到应用系统。
        不要小看这一点变化,这样一来应用系统就不需要知道公共功能模块,也就是应用系统和公共功能解耦了。公共功能会在合适的时候,由外部织入回到应用系统中,至于谁来实现这样的功能,以及如何实现不再我们的讨论之列,我们更关注这个思想。
        如果按照装饰模式来对比上述过程,业务功能对象就可以被看作是被装饰的对象,而各个公共的模块就好比是装饰器,可以透明的来给业务功能对象增加功能。
        所以从某个侧面来说,装饰模式和AOP要实现的功能是类似的,只不过AOP的实现方法不同,会更加灵活,更加可配置;另外AOP一个更重要的变化是思想上的变化——“主从换位”,让原本主动调用的功能模块变成了被动等待,甚至毫不知情的情况下被织入了很多新的功能。


2:使用装饰模式做出类似AOP的效果
        下面来演示一下使用装饰模式,把一些公共的功能,比如权限控制,日志记录,透明的添加回到业务功能模块中去,做出类似AOP的效果。
(1)首先定义业务接口
        这个接口相当于装饰模式的Component。注意这里使用的是接口,而不像前面一样使用的是抽象类,虽然使用抽象类的方式来定义组件是装饰模式的标准实现方式,但是如果不需要为子类提供公共的功能的话,也是可以实现成接口的,这点要先说明一下,免得有些朋友会认为这就不是装饰模式了,示例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 商品销售管理的业务接口
*/
public  interface  GoodsSaleEbi {
     /**
      * 保存销售信息,本来销售数据应该是多条,太麻烦了,为了演示,简单点
      * @param user 操作人员
      * @param customer 客户
      * @param saleModel 销售数据
      * @return 是否保存成功
      */
     public  boolean  sale(String user,String customer,
SaleModel saleModel);
}

顺便把封装业务数据的对象也定义出来,很简单,示例代码如下:

?
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
/**
* 封装销售单的数据,简单的示意一些
*/
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;
     }
}

 (2)定义基本的业务实现对象,示例代码如下:

?
1
2
3
4
5
6
7
8
public  class  GoodsSaleEbo implements  GoodsSaleEbi{
     public  boolean  sale(String user,String customer,
                       SaleModel saleModel) {
         System.out.println(user+ "保存了"
                                  +customer+ "购买 " +saleModel+ " 的销售数据" );
         return  true ;
     }
}

 

(3)接下来该来实现公共功能了,把这些公共功能实现成为装饰器,那么需要给它们定义一个抽象的父类,示例如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 装饰器的接口,需要跟被装饰的对象实现同样的接口
*/
public  abstract  class  Decorator implements  GoodsSaleEbi{
     /**
                * 持有被装饰的组件对象
            */
     protected  GoodsSaleEbi ebi;
     /**
      * 通过构造方法传入被装饰的对象
      * @param ebi被装饰的对象
      */
     public  Decorator(GoodsSaleEbi ebi){
         this .ebi = ebi;
     }
}

 (4)实现权限控制的装饰器
先检查是否有运行的权限,如果有就继续调用,如果没有,就不递归调用了,而是输出没有权限的提示,示例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
  * 实现权限控制
  */
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);
         }      
     }
}

 (5)实现日志记录的装饰器,就是在功能执行完成后记录日志即可,示例代码如下:

  
  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 实现日志记录
*/
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;
     }
}
 (6)组合使用这些装饰器
        在组合的时候,权限控制应该是最先被执行的,所以把它组合在最外面,日志记录的装饰器会先调用原始的业务对象,所以把日志记录的装饰器组合在中间。
        前面讲过,装饰器之间最好不要有顺序限制,但是在实际应用中,要根据具体的功能要求来,有需要的时候,也可以有顺序的限制,但应该尽量避免这种情况。
        此时客户端测试代码示例如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
     }
}

 

运行结果如下:
  

 

        好好体会一下,是不是也在没有惊动原始业务对象的情况下,给它织入了新的功能呢?也就是说是在原始业务不知情的情况下,给原始业务对象透明的增加了新功能,从而模拟实现了AOP的功能。
        事实上,这种做法,完全可以应用在项目开发上,在后期为项目的业务对象添加数据检查、权限控制、日志记录等功能,就不需要在业务对象上去处理这些功能了,业务对象可以更专注于具体业务的处理。


3.4  装饰模式的优缺点

  • 比继承更灵活
        从为对象添加功能的角度来看,装饰模式比继承来得更灵活。继承是静态的,而且一旦继承是所有子类都有一样的功能。而装饰模式采用把功能分离到每个装饰器当中,然后通过对象组合的方式,在运行时动态的组合功能,每个被装饰的对象,最终有哪些功能,是由运行期动态组合的功能来决定的。
  • 更容易复用功能
        装饰模式把一系列复杂的功能,分散到每个装饰器当中,一般一个装饰器只实现一个功能,这样实现装饰器变得简单,更重要的是这样有利于装饰器功能的复用,可以给一个对象增加多个同样的装饰器,也可以把一个装饰器用来装饰不同的对象,从而复用装饰器的功能。
  • 简化高层定义
        装饰模式可以通过组合装饰器的方式,给对象增添任意多的功能,因此在进行高层定义的时候,不用把所有的功能都定义出来,而是定义最基本的就可以了,可以在使用需要的时候,组合相应的装饰器来完成需要的功能。
  • 会产生很多细粒度对象
        前面说了,装饰模式是把一系列复杂的功能,分散到每个装饰器当中,一般一个装饰器只实现一个功能,这样会产生很多细粒度的对象,而且功能越复杂,需要的细粒度对象越多。

 


3.5  思考装饰模式

1:装饰模式的本质
        装饰模式的本质:动态组合
        动态是手段,组合才是目的。这里的组合有两个意思,一个是动态功能的组合,也就是动态进行装饰器的组合;另外一个是指对象组合,通过对象组合来实现为被装饰对象透明的增加功能。
        但是要注意,装饰模式不仅仅可以增加功能,也可以控制功能的访问,可以完全实现新的功能,还可以控制装饰的功能是在被装饰功能之前还是之后来运行等。
        总之,装饰模式是通过把复杂功能简单化,分散化,然后在运行期间,根据需要来动态组合的这么一个模式。

2:何时选用装饰模式
       建议在如下情况中,选用装饰模式:

  • 如果需要在不影响其它对象的情况下,以动态、透明的方式给对象添加职责,可以使用装饰模式,这几乎就是装饰模式的主要功能
  • 如果不合适使用子类来进行扩展的时候,可以考虑使用装饰模式,因为装饰模式是使用的“对象组合”的方式。所谓不适合用子类扩展的方式,比如:扩展功能需要的子类太多,造成子类数目呈爆炸性增长。

 


3.6  相关模式

  • 装饰模式与适配器模式
        这是两个没有什么关联的模式,放到一起来说,是因为它们有一个共同的别名:Wrapper。
        这两个模式功能上是不一样的,适配器模式是用来改变接口的,而装饰模式是用来改变对象功能的。
  • 装饰模式与组合模式
        这两个模式有相似之处,都涉及到对象的递归调用,从某个角度来说,可以把装饰看成是只有一个组件的组合。
        但是它们的目的完全不一样,装饰模式是要动态的给对象增加功能;而组合模式是想要管理组合对象和叶子对象,为它们提供一个一致的操作接口给客户端,方便客户端的使用。
  • 装饰模式与策略模式
        这两个模式可以组合使用。
        策略模式也可以实现动态的改变对象的功能,但是策略模式只是一层选择,也就是根据策略选择一下具体的实现类而已。而装饰模式不是一层,而是递归调用,无数层都可以,只要组合好装饰器的对象组合,那就可以依次调用下去,所以装饰模式会更灵活。
        而且策略模式改变的是原始对象的功能,不像装饰模式,后面一个装饰器,改变的是经过前一个装饰器装饰过后的对象,也就是策略模式改变的是对象的内核,而装饰模式改变的是对象的外壳。
        这两个模式可以组合使用,可以在一个具体的装饰器里面使用策略模式,来选择更具体的实现方式。比如前面计算奖金的另外一个问题就是参与计算的基数不同,奖金的计算方式也是不同的。举例来说:假设张三和李四参与同一个奖金的计算,张三的销售总额是2万元,而李四的销售额是8万元,它们的计算公式是不一样的,假设奖金的计算规则是,销售额在5万以下,统一3%,而5万以上,5万内是4%,超过部分是6%。
        参与同一个奖金的计算,这就意味着可以使用同一个装饰器,但是在装饰器的内部,不同条件下计算公式不一样,那么怎么选择具体的实现策略呢?自然使用策略模式就好了,也就是装饰模式和策略模式组合来使用。
  • 装饰模式与模板方法模式
        这是两个功能上有相似点的模式。
        模板方法模式主要应用在算法骨架固定的情况,那么要是算法步骤不固定呢,也就是一个相对动态的算法步骤,就可以使用装饰模式了,因为在使用装饰模式的时候,进行装饰器的组装,其实也相当于是一个调用算法步骤的组装,相当于是一个动态的算法骨架。
        既然装饰模式可以实现动态的算法步骤的组装和调用,那么把这些算法步骤固定下来,那就是模板方法模式实现的功能了,因此装饰模式可以模拟实现模板方法模式的功能。
        但是请注意,仅仅只是可以模拟功能而已,两个模式的设计目的、原本的功能、本质思想等都是不一样的。
     

 

 装饰模式结束,谢谢观赏 

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

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

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

a独家记忆
07/13
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
我的Java设计模式-代理模式

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

Jet啟思
2017/11/29
0
0
设计模式笔录(二),设计模式有哪些

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

方旭
2011/03/31
0
0
小菜学设计模式——设计模式总结之结构型

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

learn_more
2015/07/06
0
0
Java之23种设计模式解析(二)

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

wersdffg
2015/02/15
0
0
ES7 Decorator 装饰者模式

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

_朴灵_
05/14
0
0
迈向大牛的重要一步——掌握设计模式

IT职场的小菜经常有这样的疑问: 为什么一个相似的功能,大牛一会儿就搞定,然后悠闲地品着下午茶逛淘宝;而自己加班加点搞到天亮还做不完。 为什么用户提出需求变更后,大牛只需潇洒地敲敲键...

一枚Sir
2015/04/10
0
0
你需要了解的23种JavaScript设计模式

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

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

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

lxq_xsyu
2017/11/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

nodejs安装以及环境配置(很好的node安装和配置文章,少走很多弯路)

一、安装环境 1、本机系统:Windows 10 Pro(64位) 2、Node.js:v6.9.2LTS(64位) 二、安装Node.js步骤 1、下载对应你系统的Node.js版本:https://nodejs.org/en/download/ 2、选安装目录进...

sprouting
17分钟前
0
0
Redisson

了解了Redisson,发现使用挺简单的,接下来准备深入学习一下。 Redisson介绍 Redisson是架设于Redis基础之上的一个Java驻内存数据网格(In-Memory Data Grid) Redisson在基于NIO的Netty框架上...

to_ln
18分钟前
0
0
python有哪些好玩的应用实现,用python爬虫做一个二维码生成器

python爬虫不止可以批量下载数据,还可以有很多有趣的应用,之前也发过很多,比如天气预报实时查询、cmd版的实时翻译、快速浏览论坛热门帖等等,这些都可以算是爬虫的另一个应用方向! 今天给...

python玩家
18分钟前
0
0
jq 判断复选框是否被选中,复选框后台接收

1. 效果 2. 代码 html部分: JS部分: var rememberLogin = $("#rememberLoginId").is(':checked')//获取复选框是否被选中 var rememberLoginval = $("#rememberLoginId").attr('value')//拿......

Lucky_Me
25分钟前
0
0
python爬虫日志(3)-爬去异步加载网页

在浏览器检查元素页面中,选取Network中的XHR选项即可观察每次加载页面,网页发出的请求,观察url的规律即可利用封装的函数对每一页进行爬取。

茫羽行
25分钟前
0
0
《趣谈网络协议》之为什么要学习网络协议?

一、协议 1.协议的定义 简单说协议就是一个规则,保证沟通交流双方可以互相听懂、理解或者可以双方合作可以顺利进行的一个约定和规则。 2.生活中例子 (1)有一种叫“程序猿”的物种,敲着一种...

aibinxiao
27分钟前
1
0
Python数据分析numpy基础-维度的认识

什么是多维数组? 核心对象是同型的多维数组(简单理解就是一个表格,通常内容都是些数字),具有相同的数据类型。 概念: 1. axes(轴):数组的维度统称为轴。 2. rank:轴的数量称为rank。...

十年磨一剑3344
31分钟前
0
0
Java 正则表达式相关资料

1.java正则表达式过滤html标签

IT追寻者
34分钟前
0
0
点赞出现数字变大效果

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .container{ padding: 50px; border: 1px solid #dddddd; } .item{ position: relative; } ......

南桥北木
53分钟前
0
0
anroid中批量将px转换成dp

package com.qu;import java.io.File;import java.io.FileWriter;import java.io.IOException;public class Aaaa {public static void main(String[] args) {String fi......

android-key
54分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部