前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【设计模式自习室】幕后英雄:装饰模式

【设计模式自习室】幕后英雄:装饰模式

作者头像
Rude3Knife的公众号
发布2020-01-17 15:41:24
4630
发布2020-01-17 15:41:24
举报
文章被收录于专栏:后端技术漫谈

前言

《设计模式自习室》系列,顾名思义,本系列文章带你温习常见的设计模式。主要内容有:

  • 该模式的介绍,包括:
    • 引子、意图(大白话解释)
    • 类图、时序图(理论规范)
  • 该模式的代码示例:熟悉该模式的代码长什么样子
  • 该模式的优缺点:模式不是万金油,不可以滥用模式
  • 该模式的实际使用案例:了解它在哪些重要的源码中被使用

该系列会逐步更新于我的博客和公众号(博客见文章底部),也希望各位观众老爷能够关注我的个人公众号:后端技术漫谈,不会错过精彩好看的文章。

系列文章回顾

结构型——装饰器模式 Decorator Patter

引子

当你需要统计一个方法的运行时间时,你会怎么做?是不是下面这种方法呢,用两个时间语句统计出耗时?

代码语言:javascript
复制
public static void main(String[] args){
    long start = System.currentTimeMillis();
    algo(); // 执行代码块
    long end = System.currentTimeMillis();
    System.out.println(end - start);
}

如果使用了装饰器模式,你可以将统计时间的方法独立出来,变成方法上面的@Timer:

代码语言:javascript
复制
public class Foo {

    @Timer
    public void algo1() {
        ArrayList<Integer> l = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            l.add(1);
        }
    }
}

这样你就可以在不插入任何语句在原方法的情况下,统计原方法的执行时间,就像在原方法外面披了一层皮一样,这就是装饰器!

PS:如果你了解Python,那么你可能会经常使用到装饰器。在Java中,使用起来有一些复杂,但也是完全能够通过注解做到的,它可以同样应用在权限控制、日志、缓存等方面,方便的对代码进行解耦,让通用的功能“切入”原先的代码,使得开发时可以更专注于业务逻辑。

定义

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

其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。

类图

如果看不懂UML类图,可以先粗略浏览下该图,想深入了解的话,可以继续谷歌,深入学习:

装饰模式包含如下角色:

  • Component: 抽象构件
  • ConcreteComponent: 具体构件
  • Decorator: 抽象装饰类
  • ConcreteDecorator: 具体装饰类

时序图

时序图(Sequence Diagram)是显示对象之间交互的图,这些对象是按时间顺序排列的。时序图中显示的是参与交互的对象及其对象之间消息交互的顺序。

我们可以大致浏览下时序图,如果感兴趣的小伙伴可以去深究一下:

代码实现

还记得引子中举得例子吗?

我们现在Python下看看如何实现这个统计方法执行时间的函数

首先,我们定义一个函数,如下:

代码语言:javascript
复制
def exe_time(func):
    def new_func(*args, **args2):
        t0 = time.time()
        print "@%s, {%s} start" % (time.strftime("%X", time.localtime()), func.__name__)
        back = func(*args, **args2)
        print "@%s, {%s} end" % (time.strftime("%X", time.localtime()), func.__name__)
        print "@%.3fs taken for {%s}" % (time.time() - t0, func.__name__)
        return back
    return new_func

接下来,我们就可以在需要计时的函数前一行引用它作为装饰,比如:

代码语言:javascript
复制
@exe_time
def foo():
    for i in xrange(10000000):
        pass

输出效果如下:

代码语言:javascript
复制
@13:12:27, {foo} start
@13:12:27, {foo} end
@0.203s taken for {foo}

在Java中,没有Python这种天秀的语法糖,我们需要用注解实现,已经有网友写的例子,这里就不班门弄斧了。可以参考下文链接:

https://www.jianshu.com/p/d2dfb70fd671

使用场景举例

Spring

spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。

JDK源码中的使用
  • java.io.BufferedInputStream(InputStream)
代码语言:javascript
复制
InputStream inputStream = new InputStream() {
@Override
public int read() throws IOException {
return 0;
}
};
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
//增强了原本InputStream的功能
  • java.io.DataInputStream(InputStream)
  • java.io.BufferedOutputStream(OutputStream)
  • java.util.zip.ZipOutputStream(OutputStream)
  • java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap

优缺点

优点
  • 装饰模式与继承本质其其实都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  • 可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”
缺点
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

补充:装饰模式与代理模式的区别

两者都是对类的方法进行扩展,但装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上 使用增强后的功能。增强后你还是你,只不过能力更强了而已;而代理模式则强调要让别人帮你去做一 些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为 被代理的对象往往难以直接获得或者是其内部不想暴露出来。

参考

  • https://www.jianshu.com/p/d2dfb70fd671
  • https://oldj.net/blog/2010/05/22/decorator-get-execution-time
  • https://github.com/CyC2018/CS-Notes/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20%E8%A3%85%E9%A5%B0.md
  • https://blog.csdn.net/seaReal1/article/details/80252281
  • 《HEAD FIRST 设计模式》
  • 《剑指offer》

关注我

我是一名后端开发工程师。

主要关注后端开发,数据安全,爬虫,物联网,边缘计算等方向,欢迎交流。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端技术漫谈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 系列文章回顾
    • 结构型——装饰器模式 Decorator Patter
      • 引子
        • 定义
          • 类图
            • 时序图
              • 代码实现
                • 使用场景举例
                  • Spring
                    • 优缺点
                      • 补充:装饰模式与代理模式的区别
                      • 参考
                      • 关注我
                      相关产品与服务
                      边缘可用区
                      腾讯云边缘可用区(TencentCloud Edge Zone,TEZ)是腾讯云的本地扩展,适用于解决计算、存储和服务可用性问题。腾讯云边缘可用区可为您带来云的诸多优势,例如弹性、可扩展性和安全性。借助腾讯云边缘可用区,您可以在靠近最终用户的地理位置运行对延迟敏感的应用程序,基本消除延迟问题。腾讯云边缘可用区提供与中心节点一致的体验,助力业务下沉,具备更低延时、更广覆盖、更少成本等特点。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档