设计模式之装饰模式--优雅的增强
目录
- 概述
- 什么是装饰模式
- 为什么使用装饰模式
- 关键角色
- 基本代码
- 应用场景
- 版本迭代
- 版本一
- 版本二
- 版本三—装饰模式
- 装饰模式中的巧妙之处
- 1、被装饰对象和装饰对象共享相同的接口或父类
- 2、当调用装饰器类的装饰方法时,会先调用被装饰对象的同名方法
- 3、子类方法与父类方法共享相同的引用
- 4、装饰模式与继承的对比
- 总结
概述
什么是装饰模式
装饰模式(Decorator Pattern)是一种结构型设计模式,它允许通过将对象放入包含行为的特殊封装对象中来为原始对象添加新的行为。装饰模式在不改变原始对象接口的情况下,动态地将责任附加到对象上。就增加功能来看,装饰模式比生成子类更为灵活。
为什么使用装饰模式
使用装饰模式有以下几个优点:
灵活性:装饰模式允许在运行时动态地给对象添加功能,而不需要修改其代码或使用继承。这使得系统更加灵活,易于扩展和修改。
单一职责原则:装饰模式能够将不同的功能划分到不同的类中,使每个类只负责单一的功能。这遵循了单一职责原则,使得代码更加清晰、可读性更高。
可组合性:由于装饰模式使用了对象组合而不是继承,因此可以通过不同的方式将装饰器组合起来,以获得不同的行为组合。
装饰模式的结构
关键角色
- 抽象组件(Component):定义了原始对象和装饰器的公共接口,可以是接口或抽象类。
- 具体组件(Concrete Component):实现了抽象组件接口,是被装饰的原始对象。
- 装饰器(Decorator):实现了抽象组件接口,并持有一个抽象组件对象的引用,在装饰器中可以添加一些额外的行为。
-具体装饰器(Concrete Decorator):继承自装饰器,具体实现了在装饰器中定义的额外行为。
基本代码
应用场景
1、在不改变现有对象结构的情况下,对对象的功能进行扩展或修改。
2、需要动态地给对象添加功能,以便根据需要增加或移除功能。
3、对象的职责应该能够被多次扩展,而不会导致类的数量急剧增加。
版本迭代
需求:给人搭配不同的服饰
版本一
//Person
public class Person {private String name;public Person(String name ){this.name=name;}public void wearTShirts(){System.out.println("大T恤");}public void wearBigTrouser(){System.out.println("跨裤");}public void wearSneakers(){System.out.println("球鞋");}public void show(){System.out.println("装扮的"+name);}
}
//客户端
public class Client {public static void main(String[] args) {Person xc=new Person("小蔡");System.out.println("第一种装扮");xc.wearTShirts();xc.wearBigTrouser();xc.wearSneakers();xc.show();System.out.println("--------------");System.out.println("第二种装扮");xc.wearTShirts();xc.wearSneakers();xc.show();}
}
版本一非常简单,给人穿衣服就只有Person这个类,但是需求如果需要增加“超人”的装扮,就需要修改Person类(毕竟超人不是人,不能使用实例化Person,并且超人的衣服最起码是有个内裤和斗篷的~~)
这个时候怎么办,把服饰拿出去作为单独的类是不是就可以了呢
版本二
//Person
public class Person {private String name;public Person(String name){this.name=name;}public void show(){System.out.println("装扮的"+name);}
}//抽象服饰类
public abstract class Finery {public abstract void show();
}//具体的服饰类
public class BigTrouser extends Finery{@Overridepublic void show() {System.out.println("跨裤");}
}public class TShirts extends Finery{@Overridepublic void show() {System.out.println("大t恤");}
}//客户端
public class client {public static void main(String[] args) {Person xc=new Person("小蔡");Finery tt=new TShirts();Finery kk=new BigTrouser();kk.show();tt.show();xc.show();}
}
新的问题好像又来了,客户端看起来就是光着身子的小蔡一件件的穿上了衣服,也就是说穿衣服的过程不应该在客户端显示,而应该在内部完成,并且每个人的爱好不一样,穿衣服的顺序并不确定,这个建造者就不同了,而是需要把所有的功能按照正确顺序串联起来进行控制。装饰模式上场了
版本三—装饰模式
//服饰抽象类
public interface ICharacter {public void show();
}//被装饰对象人
public class Person implements ICharacter{private String name;public Person(String name){this.name=name;}@Overridepublic void show() {System.out.println("装扮的"+name);}
}
//装饰类
public class Finery implements ICharacter{protected ICharacter component;public void decorate(ICharacter component){this.component=component;}@Overridepublic void show() {if(component !=null){component.show();}}
}//具体装饰类
public class Sneakers extends Finery{public void show(){System.out.println("球鞋");super.show();}}public class BigTrouser extends Finery{public void show(){System.out.println("裤子");super.show();}}public class Tshirts extends Finery{public void show(){System.out.println("T恤");super.show();}
}//客户端
public class Client {public static void main(String[] args) {Person xc=new Person("小蔡");System.out.println("第一种装扮");Sneakers pqx=new Sneakers();pqx.decorate(xc);BigTrouser kk=new BigTrouser();kk.decorate(pqx);Tshirts tt=new Tshirts();tt.decorate(kk);tt.show();}
}
装饰模式中的巧妙之处
1、被装饰对象和装饰对象共享相同的接口或父类
这样做的目的是是为了让它们可以互相替代。这种互相替代的能力是装饰模式的关键特点之一。通过共享相同的接口或父类,可以在不修改原始对象的情况下,动态地添加、删除或更改对象的行为。
互相替换的好处在于灵活性和扩展性。当需要为对象添加额外的功能时,可以直接用装饰对象替换原始对象,而不需要修改原始对象的代码。可以将装饰器对象看作是被装饰对象的 “包装”。这样可以避免引入大量的条件语句或继承关系,使得代码更加清晰、可维护和可扩展。
另外,互相替换还可以实现功能的组合和嵌套,比如上述给人装扮的例子,穿了靴子和裤子,就是把靴子和裤子的功能进行了组合。通过将装饰器对象嵌套在其他装饰器对象中,可以构建出复杂的功能组合,以满足不同的需求。
2、当调用装饰器类的装饰方法时,会先调用被装饰对象的同名方法
例如上面装扮的例子中的show方法.装饰器类(服饰类)通过持有一个被装饰对象(component)的引用来添加额外的功能。当调用装饰器类的 show() 方法时,它会先调用被装饰对象的 show()方法,然后再执行自己的装饰逻辑(如下图所示)。这样的设计可以实现动态地扩展对象的功能,而无需改变原始对象的结构。通过嵌套和组合多个装饰器类灵活地添加、移除和组合各种功能,以满足不同的需求。
3、子类方法与父类方法共享相同的引用
在具体装饰子类中都有super.show方法,这个是调取父类的show方法
那么这样写的巧妙之处在哪呢,在这里是看不到2中说到的“”当调用装饰器类的 show() 方法时,它会先用被装饰对象的 show()方法,然后再执行自己的装饰逻辑。因为这个逻辑写到了父类中
在这里有个疑问,在具体的子类中component为什么肯定是它的上一级被装扮对象呢,比如“穿了裤子的小蔡”接着要穿T恤。这就是因为每次调用装扮对象的decorate方法,传入的都是上一级被装扮对象(如下图所示),同时子类没有重写decorate方法,此时子类方法将与父类方法共享相同的引用,并且它们将具有相同的实现和行为,也就把component传给装备对象的属性,以供后面调用show方法时使用。
4、装饰模式与继承的对比
装饰模式将功能添加到对象上的方式与继承不同。使用继承时,子类会继承父类的行为,而装饰模式可以动态地将额外的行为添加到对象上,而不需要继承。这使得装饰模式更加灵活,因为可以在运行时选择不同的行为组合。
另外,装饰模式遵循了开放-关闭原则,即对扩展开放,对修改关闭。通过组合和委托的方式,可以动态地将功能添加到对象上,而不需要修改现有代码
总结
装饰模式是一种灵活且可扩展的设计模式,它可以动态地给对象添加新的行为。使用装饰模式可以避免类爆炸问题,并能够在运行时选择不同的功能组合。它与继承相比更加灵活,符合单一职责原则,并遵循开放-关闭原则。
相关文章:

设计模式之装饰模式--优雅的增强
目录 概述什么是装饰模式为什么使用装饰模式关键角色基本代码应用场景 版本迭代版本一版本二版本三—装饰模式 装饰模式中的巧妙之处1、被装饰对象和装饰对象共享相同的接口或父类2、当调用装饰器类的装饰方法时,会先调用被装饰对象的同名方法3、子类方法与父类方法…...

前端vue,后端springboot。如何防止未登录的用户直接浏览器输入地址访问
前端,使用Vue框架来实现前端路由拦截: 设置需要登录校验的页面: 登录成功后,去设置LocalStorage里面的IsLogin为true:...
linux安装Chrome跑web自动化
添加 Chrome 源: 打开终端并执行以下命令,将 Google Chrome 的 APT 源添加到系统: bashCopy code wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 安装 Chrome: 执行以下命令来安装 Chrome&…...

linux环境下编译,安卓平台使用的luajit库
一、下载luajit源码 1、linux下直接下载: a、使用curl下载:https://luajit.org/download/LuaJIT-2.1.0-beta3.tar.gz b、git下载地址;https://github.com/LuaJIT/LuaJIT.git 2、Windows下载好zip文件,下载地址:https…...
indexedDB笔记
indexedDB 该部分内容主要源于https://juejin.cn/post/7026900352968425486 常用场景:大量数据需要缓存在本地重要概念 仓库objectStore:类似于数据库中的表,数据存储媒介索引index:索引作为数据的标志量,可根据索引获…...

系统提示缺少或找不到emp.dll文件的详细解决方案
我今天打开一款《游戏》。然而,在游戏中遇到了一个非常棘手的问题:游戏报错找不到emp.dll,无法继续执行代码。这让我们非常苦恼,因为这个问题严重影响了我们的游戏体验。 在经过一番努力之后,我终于找到了4个解决方法,…...

Python实现自动化网页操作
1 准备 推荐使用Chrome浏览器 1.1 安装selenium程序包 激活虚拟环境,打开新的Terminal,输入以下代码: python -m pip install selenium 如下图所示,表示安装成功,版本为4.7.2 安装成功 关闭虚拟环境,打…...

03 矩阵与线性变换
矩阵与线性变换 线性变换如何用数值描述线性变换特殊的线性变换反过来看总结 这是关于3Blue1Brown "线性代数的本质"的学习笔记。 线性变换 如果一个变换具有以下两个性质,我们就称它是线性的: 一是直线在变换后仍然保持为直线二是原点必须…...

MySQL InnoDB数据存储结构
1. 数据库的存储结构:页 索引结构给我们提供了高效的索引方式,不过索引信息以及数据记录都是保存在文件上的,确切说是存储在页结构中。另一方面,索引是在存储引擎中实现的,MySQL服务器上的存储引擎负责对表中数据的读…...

【数据结构】数组和字符串(十五):字符串匹配2:KMP算法(Knuth-Morris-Pratt)
文章目录 4.3 字符串4.3.1 字符串的定义与存储4.3.2 字符串的基本操作4.3.3 模式匹配算法0. 朴素模式匹配算法1. ADL语言2. KMP算法分析3. 手动求失败函数定义例1例2例3 4. 自动求失败函数(C语言)5. KMP算法(C语言)6. 失败函数答案…...
STM32 PWM可控制电压原理
PWM可控制电压原理 主要通过PWM 输入模式根据控制单位时间内输出的平均电压,以调节电压大小。而PWM输出模式通过调节占空比,控制平均电压大小; 设置TIM为PWM输出模式 第一步:时钟使能: GPIO,TIM; 第二步&a…...
angular、 react、vue框架对比
借鉴:Web前端开发:三大主流框架 (baidu.com) AngularReactVue公司ChromeFaceBook尤雨溪写法有指令、模板的概念比较灵活,没有要求使用特定的架构和模式有指令和模板的概念性能低有虚拟Dom,性能高有虚拟Dome,性能高学习门槛 高&am…...
GNSS常用数据源汇总
本文整理汇总了GNSS数据处理过程中常用的数据源,路径中的占位符具体含义如下: -YYYY-年-YY-年的后两位数-DOY-年积日-MM-月-HH-小时-WWWW-GPS周 一、RINEXO观测值与RINEXN星历小时文件 1、CDDIS:ftp://gdc.cddis.eosdis.nasa.gov/pub/gnss…...

01|LangChain | 从入门到实战-介绍
by:wenwenc9 一、基本知识储备 1、什么是大模型,LLM? 大模型(Large Language Model)是近年来一个很热门的研究方向。 使用大量的数据训练出一个非常大的模型。一般是数十亿到上万亿的参数规模。 这些大模型可以捕捉到非常复杂的语言…...

【小白专用】PHP基本语法 23.11.04
PHP基本语法 PHP是超文本预处理器 由服务器解析执行 可以与 html 进行混编(嵌入) ,PHP是一种弱类型语言 1.1 PHP标记 PHP和其他Web语言一样,都是用一对标记将PHP代码包含起来,以便和HTML代码区分开来。PHP支持4种风格的标记,如表所示。 标…...

路由器基础(七):NAT原理与配置
一、NAT 配置 华为路由器配置NAT 的方式有很多种,考试中可能考到的基本配置方 式主要有EasyIP和通过NAT地址池的方式。图22-7-1是一个典型的通过EasyIP进行NAT的示意图,其中Router出接口GE0/0/1的IP地址为200.100.1.2/24,接口E0/0/1的IP地址为192.168.0.…...

Spring Boot 整合SpringSecurity和JWT和Redis实现统一鉴权认证
📑前言 本文主要讲了Spring Security文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是青衿🥇 ☁️博客首页:CSDN主页放风讲故事 🌄每日一句:努力…...

交换机基础(零):交换机基础配置
一、华为设备视图 常用视图 名称 进入视图 视图功能 用户视图 用户从终端成功登录至设备即进 入用户视图,在屏幕上显示 kHuawei> 用户可以完成查看运行状态和统 计信息等功能。在其他视图下 都可使用return直接返回用户视 图 系统视图 在用户视图下&…...

02 线性组合、张成的空间与基
线性组合、张成的空间与基 基向量缩放向量并相加给定向量张成的空间线性相关与线性无关空间的基 这是关于3Blue1Brown "线性代数的本质"的学习笔记。 基向量 当看到一对描述向量的数时,比如[3,-2]时,把这对数中的每个数(坐标&…...

解析mfc100u.dll文件丢失的修复方法,快速解决mfc100u.dll问题
在计算机使用过程中,我们经常会遇到一些错误提示,其中最常见的就是“缺少某个文件”的错误。最近,我也遇到了一个这样的问题,那就是“mfc100u.dll丢失”。这个问题可能会导致某些应用程序无法正常运行,给我们带来困扰。…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...

听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...