设计模式之Decorator装饰者、Facade外观、Adapter适配器(Java)

news2025/1/10 16:12:52

装饰者模式

设计模式的基本原则,对内关闭修改。

Decorator Pattern,装饰者模式,也叫包装器模式(Wrapper Pattern):将一个对象包装起来,增加新的行为和责任。一定是从外部传入,并且可以没有顺序,按照代码的实际需求随意挑换顺序。当使用装饰器模式时,通常将原始对象作为一个参数传给装饰者的构造器。注重功能拓展,关注于在一个对象上动态的添加方法,在同一个方法下实现更多的功能。装饰者能够在运行时递归地被构造。

类图
在这里插入图片描述
涉及角色:

  • Component:被装饰对象基类,定义对象的接口,可以给这些对象动态增加功能;
  • ConcreteComponent:具体被装饰对象,定义具体的对象,Decorator可以给它增加额外的功能;
  • Decorator:抽象装饰者类,持有一个指向Component实例(也就是具体被装饰对象)的引用,且定义与Component一致的接口;
  • ConcreteDecorator:具体装饰者对象,实现抽象装饰者角色,负责为具体构件添加额外功能。

适用场景:

  1. 在不影响其他对象的情况下,以动态透明的方式给单个对象添加职责;
  2. 处理那些可以撤消的职责;
  3. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类

OO原则:动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。原有的不能满足现有的需求,对原有的进行增强。

装饰和继承

除了继承,装饰者模式也可以扩展行为。实际上是因为继承的弊端,大师们提出装饰者模式。

继承是面向对象编程中的一种机制,一个类可以继承另一个类的属性和方法。被继承的类称为父类或基类,继承的类称为子类或派生类。

优点

  • 代码重用:子类可重用父类的方法和属性
  • 简单明了:继承关系一目了然,易理解

缺点

  • 耦合性高:子类与父类紧密耦合,修改父类可能会影响所有子类
  • 灵活性低:继承是静态的,不能在运行时改变
  • 违背单一职责原则:子类往往会继承父类的所有功能,可能导致类职责过多

总结

  • 装饰者模式提供一种动态组合对象行为的方法,灵活性更高,遵循开闭和单一职责原则,但会增加复杂性
  • 继承提供一种静态的代码重用机制,简单明了,但耦合性高,灵活性低

选择哪种模式取决于具体需求:如果需要动态扩展对象行为,使用装饰者模式;如果希望简单地重用代码,使用继承。

实例

Java IO

BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter

当使用InputStream读取数据时,每次可能都会进行实际的IO操作,而BufferedInputStream会先将一部分数据读入缓冲区,后续的读取操作可以直接从缓冲区获取,减少IO次数。

BufferedInputStream并没有改变FileInputStream的基本结构和接口,只是为其添加缓冲特性。

Spring

Spring中的装饰者在类名上有两种表现:

  • 类名中含有Wrapper
  • 类名中含有Decorator

TransactionAwareCacheDecorator:实现Cache接口

外观模式

Facade,外观模式的定义:提供一个统一的高层接口,用来访问子系统中的一批接口,让子系统更容易使用。
在这里插入图片描述

当需要简化并统一一批接口时,考虑使用外观模式,依托于子系统执行。注重多个类的集成、统一适配。通过外观的封装,使应用程序只能看到外观对象,而不会看到具体的细节对象,降低应用程序的复杂度,提高可维护性。

该模式把一些复杂的流程封装成一个简单接口供外部用户使用,涉及3个角色:

  • 门面角色:外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定几种功能的组合。
  • 子系统角色:实现子系统的功能。它对客户角色和Facade时未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。
  • 客户角色:通过调用Facede来完成要实现的功能。

优点:

  • 解耦合:客户端和子系统解耦,让子系统内部的模块功能更容易扩展和维护;
  • 易用性:客户端不需要知道子系统内部实现或构成,只需要跟Facade类交互;
  • 层次性:有些方法是对系统外的,有些方法是系统内部相互交互的使用的。子系统把那些暴露给外部的功能集中到Facade类中,这样就可以实现客户端的使用,很好的隐藏子系统内部的细节。

满足的设计原则:莫忒耳原则,又称最少知识原则。

缺点(局限性):

  • 对子系统的所有操作都交给Facade类来处理,受到Facade类的约束;比如Facade类里可能没有对某个子系统单独的操作,如果需要,则新增方法;
  • 这样可能会导致更多的包装对象被制造出来,以处理和其他组件的沟通,可能会导致复杂度和开发时间的增加,降低运行时性能。

实例

SLF4J,参考Java日志框架SLF4J。

Spring
MeterFacade用于APM系统中的Metric监控,有3个子接口:CounterFacade、TimerFacade、GaugeFacade
在这里插入图片描述

Tomcat
RequestFacade:Request到HttpServletRequest封装
ResponseFacade:Request到HttpServletResponse封装
StandardWrapperFacade:StandardWrapper到ServletConfig封装
ApplicationContextFacade:ApplicationContext到ServletContext封装

适配器

Adapter,适配器允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。将一个对象包装起来改变接口,注重接口兼容,匹配新接口,适配类持有新的目标对象。

适配器模式有三种形式:

  • 对象适配器:通过组合满足用户期待接口,还降低代码间的不良耦合。推荐使用;适配器与适配者之间是关联(?)关系;
    在这里插入图片描述
  • 类适配器:当客户在接口中定义期望的行为时,就可以应用适配器模式,提供一个实现该接口的类,并且扩展已有的类,通过创建子类来实现适配;适配器与适配者之间是继承关系;
    在这里插入图片描述
  • 缺省适配器模式:Default Adapter Pattern,又称为单接口适配器模式,是类适配器模式的变体。当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现,那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求。
    在这里插入图片描述

涉及角色:

  • Target:目标类,客户需要的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类;
  • Adaptee:适配者类,是被适配的角色,已存在且无法被修改,因此需要被适配,一般无法获取该类的源代码;
  • Adapter:适配器类,核心类,将Adaptee和Target进行适配,即把Adaptee类转换成目标类。

优点:

  • 解耦:将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构;
  • 复用:适配者在原有系统中可正常使用,在目标类中可充当新角色;
  • 扩展:可通过配置文件很方便地更换适配器,也可在不修改原有代码的基础上增加新适配器,符合开闭原则。

对象适配器模式还有如下优点:

  • 一个对象适配器可以把多个不同的适配者适配到同一个目标;
  • 由于适配器和适配者之间是关联关系,因此可以适配一个适配者的子类,符合里氏代换原则。

缺点:

  • Java不支持多继承,一个适配器只能适配一个适配者;
  • 适配者类不能为final类;
  • 类适配器模式中的目标类只能为接口,不能为类。

对象适配器模式的缺点:当需要置换适配者类的某些方法时,需要把适配者的子类当做真正的适配者,实现过程较为复杂。

适用场景:

  • 系统需要使用现有的类,而这些类的接口不符合系统的接口;
  • 想要建立一个可重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作;
  • 两个类所做的事情相同或相似,但具有不同接口时;
  • 旧的系统开发的类已经实现一些功能,但客户端却只能以别的接口的形式访问,但不希望手动更改原有类时;
  • 使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。

实例

JDK

Java IO中,如:InputStreamReader、StringReader、OutputStreamWriter、ByteArrayInputStream等。InputStreamReader是一个适配器,Reader是适配者,InputStream是目标。ByteArrayInputStream和OutputStreamWriter同理。

使用IDEA自带的Diagrams插件查看类的依赖关系,注意要勾选Show Dependencies按钮:
在这里插入图片描述
StringReader是适配器,Reader是适配者,String是目标。
在这里插入图片描述

Spring

Spring AOP,AdvisorAdapter接口:

public interface AdvisorAdapter {
	boolean supportsAdvice(Advice advice);
	MethodInterceptor getInterceptor(Advisor advisor);
}

Advisor链需要的是MethodInterceptor(拦截器)对象,所以每一个Advisor中的Advice都要适配成对应的MethodInterceptor对象。

其实现类有3个:
在这里插入图片描述
HandlerAdapter
Spring MVC核心组件。

SourcePollingChannelAdapter

比较

适配器模式和装饰者模式

相同点:

  • 都是结构型设计模式,都使用组合思想,即通过将一个对象传递给另一个对象来实现功能的扩展或转换;
  • 两者都不会修改原有类的代码,只是通过增加新的类来实现新的功能。

不同点:

  • 目的:
    • 适配器模式旨在解决接口不兼容的问题,使现有类可以与其他类协同工作;
    • 装饰者模式则是为了动态地扩展对象的功能,而不改变其结构。
  • 实现方式:
    • 适配器模式通常涉及两个不兼容接口的转换,适配器本身只实现接口兼容,不增加新的行为;
    • 装饰者模式不改变原对象接口,通过组合和方法包装来添加新行为。
  • 适用场景:
    • 期望复用的已有的类,与新系统不兼容时,可考虑使用适配器模式;
    • 期望可以动态地为对象添加额外的功能,且功能可随时开启或关闭时,可考虑使用装饰者模式。

以Java IO流举例:

  • 将一个类适配到另一个类,InputStreamReader将Reader类适配到InputStream,实现字节流到字符流的准换;
  • FilterInputStream继承InputStream,BufferedInputStream继承自FilterInputStream,是具体的装饰器实现者,将InputStream读取的内容保存在内存中,而提高读取性能。

外观模式和装饰者模式

相同点:都是结构型设计模式,都通过组合来实现其功能,装饰者模式侧重于增强对象的功能,而外观模式侧重于简化系统接口。

不同点:

  • 目的:
    • 装饰者模式的目的是通过包装对象来动态扩展其功能;
    • 外观模式的目的是通过提供一个简化的接口来隐藏系统复杂性。
  • 实现方式:
    • 装饰者模式在同一个接口层次上通过递归组合的方式增加行为;
    • 外观模式通过创建一个高层接口来简化对复杂子系统的访问。
  • 使用场景:
    • 装饰者模式适用于需要动态扩展对象功能的场景;
    • 外观模式适用于通过封装来简化与复杂子系统交互的场景。

代理模式和装饰者模式

与原对象实现同一个接口,必须要实现原接口和持有真实的对象,才能称之为代理类。代理模式一定是自身持有这个对象,不需要从外部传入。用代理模式,代理类可以对它的客户隐藏一个对象的具体信息。当使用代理模式时,常常在一个代理类中创建一个对象的实例。注重隔离限制,关注于控制对对象的访问,让外部不能访问你实际的调用对象,如权限控制。代理和真实对象之间的关系通常在编译时就已经确定。

参考

  • 适配器模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2065272.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qml 实现仿前端的 Notification (悬浮出现页面上的通知消息)

【写在前面】 经常接触前端的朋友应该经常见到下面的控件: 在前端中一般称它为 Notification 或 Message,但本质是一种东西,即:悬浮弹出式的消息提醒框。 这种组件一般具有以下特点: 1、全局/局部显示:它不…

基于单片机的信号发生器设计

本设计采用了STM32F103C8T6单片机作为控制核心,通过控制DDS模块产生不同频率且高稳定和低失真的信号,再通过放大电路对信号的幅值进行放大。此外通过按键可以使用户对频率进行调节以及对输出波形进行切换,由于AD9833输出的幅值是固定的&#…

启动docker镜像

1、运行容器 2、当前运行的进程 3、当前位置和启动时间 4、cat/etc/redhat-release查看版本 5.镜像是模版,容器是实例 6.容器中没有命令运 7.容器总是能轻易获取 8.配置yum 9.安装http 10.修改index⽂件 11.httpd -k start 12.访问 13.退出就没有服务了 14…

细谈LCM驱动电压VGHVGL电路原理

前言: ***在液晶显示屏驱动电路中,VGH电压负责对TFT栅极电容进行充电开启,并使电容电压保持一个场周期,VGL电压负责TFT栅极的关闭。 如果VGH和VGL电压出现不稳或者幅度变化,都会引起图像显示故障,例如花屏…

委托发布 | 进迭时空联合移动云能力中心实现业界首个RISC-V IO虚拟化方案

仟江水商业电讯(8月22日 北京 委托发布)虚拟化是云计算技术基石,是RISC-V走进云计算等高性能计算场景的必然要求。RISC-V国际基金会2021年制定了Hypervisor 1.0规范,2023年制定了AIA 1.0规范和IOMMU 1.0规范,这3个规范…

CentOS 7使用RPM安装MySQL5.7

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言1:下载MySQL5.7的rpm安装包2:卸载已安装的MySQL(没安装过则跳过)3:MySQL安装环境准备4:安…

网络竞赛可视化:打造线上赛事

通过图扑网络竞赛可视化,可以实时跟踪和分析参赛者的表现,直观展示比赛进程和结果。这不仅提高了观赛体验,还帮助组织者更有效地管理和优化赛事。

STM32——SPI通信协议以及软件读写

1、SPI协议 SPI相对于I2C传输速度更快;设计简单,通信协议使用硬件线比较多,有些资源浪费 以下设备需要进行共地,如果从机没有独立的供电源,主机需要给供电 SS线低电平有效,主机只能选择一个从机 推挽输出…

Spring + Boot + Cloud + JDK8 + Elasticsearch 单节点 模式下实现全文检索高亮-分页显示 快速入门案例

1. 安装elasticsearchik分词器插件 sudo wget https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-8.13.4.zip sudo mkdir -p ./es_plugins/analysis-ik sudo mkdir ./es_data sudo unzip elasticsearch-analysis-ik-8.13.4.zip -d ./es_plugins/a…

SpringCloudAlibaba Seata分布式事务

分布式事务 事务是数据库的概念,数据库事务(ACID:原子性、一致性、隔离性和持久性); 分布式事务的产生,是由于数据库的拆分和分布式架构(微服务)带来的,在常规情况下,我们在一个进…

自己动手写CPU_step4_逻辑运算|移位指令

序 上一篇中我们解决了流水线的数据相关问题,本篇将添加多条逻辑运算和移位运算指令,这些指令的格式严格按照MIPS的指令格式设计。 MIPS指令格式 由于本人也是处于学习的一个过程,如有不对之处,还请大牛指正。 就逻辑运算和移位运…

【软件逆向】第11课,软件逆向安全工程师之windows API函数,每天5分钟学习逆向吧!

资料获取 关注作者,备注课程编号,获取本课配套课件和工具程序。 干货开始-windows API函数。 微软官方提供的应用程序接口,是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件提供的能力。 地址:h…

java基础 之 了解final

文章目录 定义使用及规则修饰类修饰方法修饰变量修饰成员变量修饰局部变量final与static共同修饰变量final修饰的变量和普通变量的区别 本篇文章代码就不附上了,建议大家实际敲一敲,更能加快理解 定义 final表示”最后的,最终的“含义&#…

精益思维赋能机器人行业的三大维度

在日新月异的科技浪潮中,机器人行业正以前所未有的速度蓬勃发展,成为推动产业升级与转型的关键力量。然而,如何在激烈的市场竞争中脱颖而出,实现高效、灵活与可持续的发展?精益思维,这一源自制造业的管理哲…

【el-switch更改高、宽、颜色样式】深入浅出element ui的switch同页面存在多个更改样式互不影响

1.技术: “vue”: “^2.6.14”, “element-ui”: “^2.15.6”, 2.需求: 同一个页面存在多个switch组件时, 需要更改各自的高度、宽度、选择颜色、非选中颜色等样式, 并且样式隔离互不影响! 3.效果图: 4.重要…

C++动态规划(背包问题)

目录 一:动态规划是什么 二.动态规划的运用 (1).用动态规划解决重复子问题 (2).动态规划使用的条件与流程 Ⅰ.动态规划的使用条件: Ⅱ.动态规划的使用流程 (3).背包问题 三.…

IO进程(学习)2024.8.22

信号 信号函数 信号处理函数 #include <signal.h> sighandler_t signal(int signum, sighandler_t handler); 功能&#xff1a;信号处理函数 参数&#xff1a;signum&#xff1a;要处理的信号 handler&#xff1a;信号处理方式 SIG…

基于矢量控制器的PMSM永磁同步电机速度控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于矢量控制器的PMSM永磁同步电机速度控制系统simulink建模与仿真&#xff0c;仿真输出电机转速跟踪曲线&#xff0c;PID控制器输出曲线以及Te输出曲线。 2.系统仿真结果 &…

kafka的一个有趣问题(BUG)

这是我的第104篇原创文章 问题由来 在使用kafka时&#xff0c;创建topic&#xff0c;对某个topic进行扩分区的操作&#xff0c;想必大家肯定都使用过。尤其是集群进行扩容时&#xff0c;对流量较大的topic进行扩分区操作。一般而言&#xff0c;期望的效果是&#xff1a;新扩的分…

AI在医学领域:HYDEN一种针对医学图像和报告的跨模态表示学习方法

近年来&#xff0c;跨模态文本-图像表示学习在诸多领域取得了显著的突破&#xff0c;尤其是在零样本学习和图像-文本检索等任务上。这一成果的取得很大程度上归功于大量弱监督的图像-文本配对数据的利用&#xff0c;这些数据有效地增强了视觉-语言表示学习的能力。在医学成像领…