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

浅谈装饰模式及其在JDK、Flink中的应用

前言上周末在家翻看之前写的部分文章,发现在设计模式方面甚少涉猎。在阅读开源项目源码的过程中,我们经常会接触到各种设计模式,深入理解它们无

前言

上周末在家翻看之前写的部分文章,发现在设计模式方面甚少涉猎。在阅读开源项目源码的过程中,我们经常会接触到各种设计模式,深入理解它们无疑大有裨益,能够帮助我们快速get到那些masterminds背后的思想。今天就来谈一谈应用较为广泛的装饰模式


装饰模式与四要素


所谓装饰模式(decorator pattern),就是在不改变原有类,也不影响其他继承自该类的子类的行为的基础上,为原有类在运行期动态地添加新行为的模式。


我们知道,类继承是为类扩充功能的一般方案。而装饰模式作为类继承的替代方案存在,其意义在于:


类继承扩充的功能在编译期就被确定,而装饰模式扩充的功能可以在运行时由调用方确定。如果要为类同时扩充多个相互独立而又可以组合的功能,采用类继承方案就意味着为每种组合创建新的类,容易造成子类泛滥。装饰模式就可以灵活地按需组合(就像现实中的小装饰品可以随意摆放一样),更加简洁且易于修改。


下面的UML类图示出实现装饰模式的四要素。


  • 构件(Component):接口,用于定义整个实体空间的最基础的行为规范;
  • 构件实体(ConcreteComponent):实现Component的实体类,本身具有一些功能,同时也是被装饰(被扩充)的类;
  • 装饰器(Decorator):实现Component的类,其中维护一个ConcreteComponent的实例,具体的装饰功能由其子类实现;
  • 装饰器实体(ConcreteDecorator):继承Decorator并实现具体的装饰功能。

通过下面两句话即可使用装饰器实体ConcreteDecorator实现的扩充功能:

 

Component component = new ConcreteDecorator(new ConcreteComponent());
component.operation();

可见,调用方只需要额外调用装饰器实体的构造函数,而不必关心Component/ConcreteComponent在装饰之后的变化。不过由上也可以看出,装饰模式会new出更多的对象,当装饰器实体的链比较长时会有性能问题,并且出现问题时也不利于debug。

上面所有内容讲的装饰模式叫做透明装饰模式,即用户总可以只用Component来调用所有功能。相对地,还有一种半透明装饰模式,即装饰器实体中允许存在Component中不存在的新方法(如someNewBehavior()),调用方式相应就变成:

 

ConcreteDecorator component = new ConcreteDecorator(new ConcreteComponent());
component.someNewBehavior();

由于扩充功能可以在新方法中定义,半透明装饰模式更加灵活,但是就无法对用户屏蔽ConcreteDecorator存在的现实了。更重要的是,半透明装饰模式下对实例进行多次(链式)装饰是没有意义的,因为只能调用最后一次装饰时装饰器实体的新增方法。

干说了这么多,举两个示例来帮助理解吧。


Java I/O中的装饰模式

装饰模式在java.io包中广泛使用,包括基于字节流的InputStream/OutputStream和基于字符的Reader/Writer体系。以下以InputStream为例。

InputStream是所有字节输入流的基类,其下有众多子类,如基于文件的FileInputStream、基于对象的ObjectInputStream、基于字节数组的ByteArrayInputStream等。有些时候,我们想为这些流加一些其他的小特性,如缓冲、压缩等,用装饰模式实现就非常方便。相关的部分类图如下所示。

这个类图很标准,其中:


  • 构件是InputStream;
  • 构件实体是FileInputStream、ObjectInputStream等等;
  • 装饰器是FilterInputStream;
  • 装饰器实体是FilterInputStream的所有子类。

观察一下装饰器FilterInputStream的开头,可以发现它持有InputStream的引用,并且实现了InputStream中的所有方法(实际上就是简单地代理了一下)。具体的装饰器实体就继承FilterInputStream,并实现对应的扩充功能。如下图所示。

以下就可以用BufferedInputStream和GZIPInputStream创建一个带缓冲、压缩的文件输入流。

 

InputStream is = new GZIPInputStream(new BufferedInputStream(new FileInputStream("test.txt")));

当然,如果我们想要自己实现一个InputStream的装饰器实例,创建一个FilterInputStream的子类即可,就不再举例了。


Flink State TTL中的装饰模式

笔者之前写过一篇文章《简析Flink状态生存时间(State TTL)机制的底层实现》,这里就用到了装饰模式,但不像Java I/O那样标准。

为状态增加TTL的特性可以直接在原始状态之上实现,因此符合装饰模式的场景。Flink引入了一个AbstractTtlDecorator抽象类作为装饰器,负责为状态类型T装饰上与TTL相关的基本逻辑。相关的部分类图如下所示。

可见,虽然AbstractTtlDecorator并未持有State的实例(只有State的类型参数),但是在其子类AbstractTtlState中,通过持有TTL状态上下文TTLStateContext间接地得到了State实例。例如,由AbstractTtlState派生出来的TtlMapState直接在原来的MapState上进行增删改查操作,只是附带上了AbstractTtlDecorator和AbstractTtlState提供的TTL逻辑而已。其他的TtlListState等也是同理。具体的代码可参见前面给的传送门,这里不再重复贴了。

虽然这种模式的类结构并不典型,但是也完全契合装饰模式的精神,Ttl*State对用户也是透明的。有很多开源框架都采用了这种相对松散的装饰模式,有时会被称为包装(Wrapper)模式。


The End

明天高考,各位小盆友加油加油。

民那晚安晚安。


推荐阅读
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
  • 属性类 `Properties` 是 `Hashtable` 类的子类,用于存储键值对形式的数据。该类在 Java 中广泛应用于配置文件的读取与写入,支持字符串类型的键和值。通过 `Properties` 类,开发者可以方便地进行配置信息的管理,确保应用程序的灵活性和可维护性。此外,`Properties` 类还提供了加载和保存属性文件的方法,使其在实际开发中具有较高的实用价值。 ... [详细]
  • Flowable 流程图路径与节点展示:已执行节点高亮红色标记,增强可视化效果
    在Flowable流程图中,通常仅显示当前节点,而路径则需自行获取。特别是在多次驳回的情况下,节点可能会出现混乱。本文重点探讨了如何准确地展示流程图效果,包括已结束的流程和正在执行的流程。具体实现方法包括生成带有高亮红色标记的图片,以增强可视化效果,确保用户能够清晰地了解每个节点的状态。 ... [详细]
  • 【问题】在Android开发中,当为EditText添加TextWatcher并实现onTextChanged方法时,会遇到一个问题:即使只对EditText进行一次修改(例如使用删除键删除一个字符),该方法也会被频繁触发。这不仅影响性能,还可能导致逻辑错误。本文将探讨这一问题的原因,并提供有效的解决方案,包括使用Handler或计时器来限制方法的调用频率,以及通过自定义TextWatcher来优化事件处理,从而提高应用的稳定性和用户体验。 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • 在Android平台中,播放音频的采样率通常固定为44.1kHz,而录音的采样率则固定为8kHz。为了确保音频设备的正常工作,底层驱动必须预先设定这些固定的采样率。当上层应用提供的采样率与这些预设值不匹配时,需要通过重采样(resample)技术来调整采样率,以保证音频数据的正确处理和传输。本文将详细探讨FFMpeg在音频处理中的基础理论及重采样技术的应用。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • DVWA学习笔记系列:深入理解CSRF攻击机制
    DVWA学习笔记系列:深入理解CSRF攻击机制 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • Silverlight 实战指南:深入解析用户提交数据的验证与捕获机制
    本文深入探讨了Silverlight中用户提交数据的验证与捕获机制,详细分析了四种主要的验证方法:基本异常处理、DataAnnotation注解、IDataErrorInfo客户端同步验证以及自定义验证策略。通过实例解析,帮助开发者更好地理解和应用这些机制,提升应用程序的数据处理能力和用户体验。 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • 本文深入解析了WCF Binding模型中的绑定元素,详细介绍了信道、信道管理器、信道监听器和信道工厂的概念与作用。从对象创建的角度来看,信道管理器负责信道的生成。具体而言,客户端的信道通过信道工厂进行实例化,而服务端则通过信道监听器来接收请求。文章还探讨了这些组件之间的交互机制及其在WCF通信中的重要性。 ... [详细]
  • ### 优化后的摘要本学习指南旨在帮助读者全面掌握 Bootstrap 前端框架的核心知识点与实战技巧。内容涵盖基础入门、核心功能和高级应用。第一章通过一个简单的“Hello World”示例,介绍 Bootstrap 的基本用法和快速上手方法。第二章深入探讨 Bootstrap 与 JSP 集成的细节,揭示两者结合的优势和应用场景。第三章则进一步讲解 Bootstrap 的高级特性,如响应式设计和组件定制,为开发者提供全方位的技术支持。 ... [详细]
author-avatar
CQ莹儿_259
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有