当前位置: 首页 > news >正文

JS设计模式之装饰者模式:优雅的给对象增添“魔法”

image.png

引言

在前端开发中,我们经常会遇到需要在不修改已有代码的基础上给对象添加新的行为或功能的情况。而传统的继承方式并不适合这种需求,因为继承会导致类的数量急剧增加,且每一个子类都会固定地实现一种特定的功能扩展。

装饰者模式则提供了一种更加灵活的解决方案。它可以在运行时动态地给对象新的功能,同时避免了继承带来的类爆炸问题。

本篇文章将会详细介绍 JavaScript 设计模式装饰者模式。通过阅读本文,你将了解到如使用装饰者模式来动态地扩展对象的功能,同时保持代码的灵活性和可维护性。

一. 什么是装饰者模式

定义

装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许将对象包在其他对象中,而无需改变对象的原始结构,从而动态地为其添加新的行为和功能,并且不会改变原有对象的结构。

装饰者模式通过将对象包装到一个装饰器中,将新的行为包裹在原始对象周围,以增强其功能。这种模式通过使用组合而不是继承的方式,可以在运行时动态地添加、删除或修改对象的功能。

核心思想

装饰者模式的核心思想是通过组合来实现功能的扩展,而不是通过继承。通过将对象包装进装饰器对象中,可以在需要的时候像堆叠木块一样一层层地添加功能,也可以在不需要某个功能时轻松地移除它。

使用装饰者模式可以提供灵活性和可扩展性,同时也遵循开闭原则,即对修改关闭,对扩展开放。它可以帮助我们避免继承链的臃肿和复杂化,将对象的功能拆分为不同的装饰器,使得代码更加可维护和可复用。

主要特点

装饰者模式具有以下主要特点:

  1. 动态添加行为:装饰者模式允许在运行时动态地向对象添加新的行为,而不需要修改已有的代码或对象的结构。通过将对象包装在装饰器中,可以在不改变原始对象的情况下,增加、修改或删除对象的功能。

  2. 组合而非继承:装饰者模式通过组合而不是继承的方式,实现了对对象的功能扩展。不同于继承链的方式,装饰者模式允许你根据需要灵活地组合多个装饰器,以实现不同的功能组合。

  3. 透明性:装饰者模式使得装饰器和原始对象具有相同的接口,这意味着对于使用对象的客户端来说,无论是使用原始对象还是装饰器对象,都可以一致地进行操作,而不会产生任何混淆。

  4. 可逆性:装饰者模式允许随时添加、删除或修改对象的行为,因此具有可逆性。即如果你不再需要某个装饰器的功能,可以很容易地将其从装饰器堆栈中移除,恢复到原始对象的状态。

  5. 灵活性和可扩展性:通过装饰者模式,可以灵活地扩展对象的功能,而无需修改原始对象或其他装饰器。你可以根据需要组合不同的装饰器,构建出复杂的功能组合,同时保持代码的可维护性和可复用性。

总的来说,装饰者模式提供了一种灵活、可扩展和可逆的方式来动态地添加对象的行为。它使得功能扩展变得简单,同时通过组合而非继承的方式,避免了继承链的复杂性和僵化性。这使得代码更加灵活、可维护和可复用。

二. 装饰者模式的结构

装饰者模式的结构主要包含以下几个角色:

  1. 组件(Component):定义一个接口或抽象类,作为装饰器和具体组件对象的公共接口。它可以是一个类或者接口,它声明了具体组件和装饰器需要实现的方法。

  2. 具体组件(ConcreteComponent):实现了组件接口,也就是被装饰的对象。它是原始对象,具有基本的功能。

  3. 装饰器(Decorator):实现了组件接口,并持有一个被装饰的组件对象的引用。装饰器通过对被装饰对象的包装,可以在不修改原始对象的基础上,动态地添加额外的行为。它具有与组件相同的接口,可以递归地包装其他装饰器或具体组件。

  4. 具体装饰器(ConcreteDecorator):扩展了装饰器类,实现了具体的装饰逻辑。具体装饰器可以在调用被装饰对象的方法之前或之后,添加额外的行为。

下面是 JavaScript 装饰者模式的结构示意图:

image.png

在这个结构中,具体组件(ConcreteComponent)是被装饰的原始对象,它实现了组件接口,并具有基本的功能。装饰器(Decorator)也实现了组件接口,并持有一个被装饰的组件对象的引用。具体装饰器(ConcreteDecorator)扩展了装饰器类,可以在调用被装饰对象的方法之前或之后,添加额外的行为。

通过组件接口的统一,装饰器和具体组件对象可以互相替换,使得客户端可以透明地使用装饰后的对象。可以根据需求灵活地组合装饰器,实现不同的功能组合。

注意:装饰者模式中的装饰器和具体组件对象之间是松耦合的关系,它们之间通过共同的接口进行交互,不依赖具体的实现,从而实现了动态扩展和变更功能的目的。

三. 如何实现装饰者模式

装饰者模式的实现步骤可以分为以下几个步骤:

  1. 定义组件接口或抽象类

定义一个基础的组件接口或抽象类,它是被装饰者和装饰器共同实现的接口。

// 组件接口或抽象类
class Component {operation() {}
}
  1. 实现具体组件类

创建一个实现了组件接口或抽象类的具体组件类,也就是被装饰者。

// 具体组件类
class ConcreteComponent extends Component {operation() {console.log("执行具体组件的操作");}
}
  1. 定义装饰器抽象类

创建一个装饰器抽象类,继承自组件接口或抽象类,它将持有一个被装饰的组件对象。

// 装饰器抽象类
class Decorator extends Component {constructor(component) {super();this.component = component;}operation() {this.component.operation();}
}
  1. 实现具体装饰器类

创建具体的装饰器类,继承自装饰器抽象类,可以在不修改原有对象的情况下,为它添加额外的行为。

// 具体装饰器类A
class ConcreteDecoratorA extends Decorator {operation() {super.operation();this.addBehaviorA();}addBehaviorA() {console.log("添加额外的行为A");}
}// 具体装饰器类B
class ConcreteDecoratorB extends Decorator {operation() {super.operation();this.addBehaviorB();}addBehaviorB() {console.log("添加额外的行为B");}
}
  1. 创建装饰链

可以按需创建装饰链,将具体的装饰器对象以特定的顺序组合在一起。

// 创建被装饰的具体组件对象
const component = new ConcreteComponent();// 创建具体装饰器对象A,并传入component
const decoratorA = new ConcreteDecoratorA(component);// 创建具体装饰器对象B,并传入decoratorA
const decoratorB = new ConcreteDecoratorB(decoratorA);
  1. 调用装饰后的对象方法

通过装饰后的对象来调用方法,观察装饰器是否成功地添加了额外的行为。

// 调用装饰后的对象的方法
decoratorB.operation();

在上述步骤中,我们首先定义了一个抽象类 Component 作为组件的基本接口。然后创建了具体组件类 ConcreteComponent,实现了组件接口的 operation 方法。

接下来,我们定义了一个装饰器抽象类 Decorator,其构造函数接收一个组件对象,通过调用组件对象的 operation 方法来实现组件的操作。

然后,我们创建了两个具体装饰器类 ConcreteDecoratorAConcreteDecoratorB,它们继承自装饰器抽象类,并实现了自己的增加行为的方法。

最后,我们创建了被装饰的具体组件对象 component,然后按照一定顺序创建了具体装饰器对象 decoratorAdecoratorB,并将它们串联起来形成装饰链。最后,调用装饰后的对象 decoratorBoperation 方法,观察它的输出。

这样,通过装饰器模式,我们可以在不改变原有对象的情况下,动态地扩展对象的功能。

四. 装饰者模式的应用场景

在 JavaScript 中,装饰者模式可以应用于各种场景,用于动态地给对象添加额外的功能或行为。以下是一个详细的代码分析,展示了 JavaScript 装饰者模式是如何应用的。

假设我们有一个简单的组件,用于展示用户的个人信息,包括姓名、年龄和职业。我们希望能够根据用户的权限动态地添加一些额外功能,比如显示用户的手机号码或地址,同时保持代码的灵活性。

  1. 首先,我们创建一个基础的用户信息组件 UserInfo

class UserInfo {constructor(name, age, occupation) {this.name = name;this.age = age;this.occupation = occupation;}render() {console.log(`Name: ${this.name}`);console.log(`Age: ${this.age}`);console.log(`Occupation: ${this.occupation}`);}
}
  1. 然后,我们创建一个装饰者类 PhoneDecorator,用于在用户信息中添加显示手机号码的功能:

class PhoneDecorator {constructor(userInfo, phoneNumber) {this.userInfo = userInfo;this.phoneNumber = phoneNumber;}render() {this.userInfo.render();console.log(`Phone: ${this.phoneNumber}`);}
}
  1. 接下来,我们创建另一个装饰者类 AddressDecorator,用于在用户信息中添加显示地址的功能:

class AddressDecorator {constructor(userInfo, address) {this.userInfo = userInfo;this.address = address;}render() {this.userInfo.render();console.log(`Address: ${this.address}`);}
}
  1. 现在,我们可以使用这些装饰者来动态地添加功能。下面是应用装饰者模式的示例代码:

// 创建基础的用户信息对象
const userInfo = new UserInfo("John Doe", 30, "Engineer");// 创建装饰者对象并应用装饰器
const phoneDecorator = new PhoneDecorator(userInfo, "1234567890");
const addressDecorator = new AddressDecorator(phoneDecorator, "123 Main St");// 渲染用户信息
addressDecorator.render();

输出结果将会是:

Name: John Doe
Age: 30
Occupation: Engineer
Phone: 1234567890
Address: 123 Main St

通过使用装饰者模式,我们可以动态地为用户信息对象添加不同的功能组合,而不需要修改原始对象,从而实现了代码的灵活性和扩展性。

注意:以上代码只是一个简化的示例,实际应用中可能会更加复杂。装饰者模式的实现也可以有很多变体,具体的实现方式可以根据需求和设计的复杂度来决定。

五. 装饰者模式的优缺点

装饰者模式的优点

  1. 动态扩展:装饰者模式允许在运行时动态为对象添加新的功能或行为,而无需修改原始对象的结构。这使得代码更加灵活,能够根据需求动态地组合和应用装饰器。

  2. 开闭原则:装饰者模式符合开闭原则,可以在不修改已有代码的情况下扩展新的功能。通过添加新的装饰器,可以在不改变原始对象的代码的前提下,扩展和修改对象的行为。

  3. 单一职责原则:装饰者模式将具体功能的实现分散到不同的装饰器类中,每个装饰器只关注自己的功能实现,使得代码结构清晰,符合单一职责原则。

  4. 组合灵活:通过灵活组合不同的装饰器,可以实现多种功能组合,满足不同的需求。装饰器模式提供了一种仅通过不同的组合方式就可以实现复杂功能的便捷方法。

装饰者模式的缺点

  1. 复杂性增加:装饰者模式引入了大量的类和对象,使得代码结构变得复杂。在设计和理解装饰器链的同时,需要考虑装饰器之间的关系和顺序。

  2. 运行时性能开销:由于装饰者模式是通过多层嵌套的方式来添加功能的,每个装饰器都会增加一次方法调用的开销。这可能在性能敏感的场景中产生一定的性能损失。

  3. 额外对象的创建:每个装饰器需要持有一个被装饰的对象,这样会增加多个对象的创建和维护成本。在需要大量对象时,可能会占用较多的内存空间。

综上所述,装饰者模式提供了动态扩展和灵活组合功能的方式,符合开闭原则和单一职责原则,但也可能导致代码复杂性增加和运行时性能开销。在使用时需要根据实际情况进行权衡和选择。

总结

在本篇文章中,我们详细解析了 JavaScript 中的装饰者模式及其应用。装饰者模式是一种结构型设计模式,通过动态地给对象添加新的行为,实现了功能的扩展和组合,同时遵循开闭原则和单一职责原则。

在使用装饰者模式时,我们需要注意合理地设计装饰者类的层级结构,避免过多的装饰者嵌套导致代码复杂度的增加。同时,要确保每个装饰者类的职责单一,只关注一个特定的功能扩展。

装饰者模式在实际开发中有着广泛的应用,如日志记录、性能监测、权限验证等。它不仅提供了一种灵活的扩展方式,还能帮助我们解耦和复用代码。

通过学习和理解装饰者模式,我们能够更加灵活地设计和开发 JavaScript 应用,提高代码的可扩展性和维护性。

相关文章:

JS设计模式之装饰者模式:优雅的给对象增添“魔法”

引言 在前端开发中,我们经常会遇到需要在不修改已有代码的基础上给对象添加新的行为或功能的情况。而传统的继承方式并不适合这种需求,因为继承会导致类的数量急剧增加,且每一个子类都会固定地实现一种特定的功能扩展。 装饰者模式则提供了…...

准备好了吗?JAVA从业AI开发的学习路线详解

作为一个拥有扎实 Java 基础的人,想要涉足人工智能(AI)应用开发,你已经在编程能力方面打下了很好的基础。Java 是一种通用的、强类型的语言,非常适合于开发高性能的应用程序,尤其是在后端服务和大规模分布式…...

神经网络通俗理解学习笔记(1)

神经网络通俗理解学习笔记(1) 神经网络原理激活函数前向传播和反向传播多层感知机代码实现加载数据网络结构损失函数优化器训练测试保存 回归问题一元线性回归多元线性回归多项式回归 线性回归代码实现数据生成设置超参数初始化参数可视化Pytorch模型实现…...

有n个人,他们需要分配m元钱(m>n),每个人至少分到1元钱,且每个人分到的钱数必须是整数。请问有多少种分配方案?

分配方案 描述 有n个人,他们需要分配m元钱(m>n),每个人至少分到1元钱,且每个人分到的钱数必须是整数。请问有多少种分配方案? 输入 一行,两个整数,分别是人数n与钱数m,用一个空格隔开。 输出 一行&am…...

光耦——创新引擎 助推中国经济高质量发展

近年来,中国经济正处于转型升级的关键时期,高质量发展成为经济发展的重要目标。在这一伟大征程中,光耦作为一种关键性的电子元器件,正在发挥着重要的作用,助力中国经济迈向更加光明的未来。 光耦概念及工作原理 ▲光耦…...

Go 中 RPC 的使用教程

前言 RPC(Remote Procedure Call)是一种允许程序调用远程服务器上函数的方法,调用过程对于开发者来说像是调用本地函数一样方便。Go 语言自带了强大的 net/rpc 库,能够让开发者轻松实现基于 Go 的 RPC 服务。本文将介绍 Go 中 RP…...

挖耳勺可以伸进耳朵多深?安全可视挖耳勺推荐!

一般来说,挖耳勺不应该伸进耳朵太深,外耳道的长度大约在2.5厘米到3.5厘米之间,但不建议将挖耳勺伸进超过外耳道外1/3的深度,也就是大概1厘米左右较为安全。因为如果伸得太深,很容易损伤外耳道皮肤,引起疼痛…...

SuperMap GIS基础产品FAQ集锦(20240911)

一、SuperMap iObjects Java 问题1:【iObject Python】Objects Python产品有哪些能力特性和优势? 11.2.0 【解决办法】iObjects Python产品包含传统GIS功能(基于iObjects Java扩展的功能接口)和AI GIS功能模块。 其中传统GIS功能…...

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compo…...

ChatGPT提示词优化大师使用指南

我希望你成为我的ChatGPT提示词优化大师。 您的目标是帮助我根据自己的需要制定尽可能最好的提示。 你提供的提示应该是站在我向ChatGPT发起请求的角度来写的。我的初始提示词如下:此处填入你的初始提示词 ChatGPT提示词生成器 我希望你充当提示词生成器。 比如&…...

计算机毕业设计 智能推荐旅游平台 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点…...

【拥抱AI】基于多种数据分段工具的优缺点分析

最近在深入了解RAG方面的知识,其中数据清洗和数据分段是创建知识库的重要步骤。数据清洗目前暂时选用了MinerU,然后就需要针对数据分段进行选型。 以下是我了解到的几种数据分段工具,简单总结了一下它们的优缺点,权当笔记分享&am…...

在 Windows 系统上,文件传输到虚拟机(VM)可以通过 VS Code 的图形界面(GUI)或命令行工具进行操作

在 Windows 系统上,文件传输到虚拟机(VM)可以通过 VS Code 的图形界面(GUI)或命令行工具进行操作。以下是几种方法: ### 方法 1: 使用 VS Code 图形界面 1. **连接到远程 VM**: - 在 VS Cod…...

kafka的主要功能

Apache Kafka 是一个分布式流处理平台,它最初由 LinkedIn 开发,后来捐赠给了 Apache Software Foundation,并成为了 Apache 的顶级项目。Kafka 设计用于处理实时数据流,并且提供了高性能、可扩展性和持久性。下面是 Kafka 的主要功…...

vue3中provide和inject详解

provide和inject是什么 provide 和 inject 是 Vue.js 框架中提供的一种依赖注入机制。这种机制允许一个祖先组件(提供者)向其所有子孙组件(使用者)提供数据或方法,而不需要通过逐层组件传递属性(props&…...

相约华中科技大学,移动云技术论坛来了!NineData创始人CEO叶正盛将分享《数据库全球实时传输技术实践》的主题演讲

2024年9月12日,中国移动云能力中心将在华中科技大学举办“智算浪潮下数据库发展论坛”,共同探讨数据库技术与应用的创新,分享算力网络时代数据库未来发展的洞见。本次论坛,NineData 创始人&CEO 叶正盛受邀参会,并来…...

华为 昇腾 310P 系列 AI 处理器支持 140Tops 的 AI 算力。

1、产品简介 模组是基于昇腾 310P 系列 AI 处理器设计而成,可实现图像、视频等多种数据分析 与推理计算。超强的视频编解码能力以及支持 140Tops 的 AI 算力。在边缘侧及端侧的嵌入式计算 领域,有着极高的性价比,具有超强算力、 超高能效、…...

基于单片机的小型生态鱼缸控制器设计

本设计以STC89C52单片机为核心,利用DS18B20温度传感器和LCD1602液晶显示器实时采集和显示当前环境温度,并根据与预设温度阈值的比较结果控制加热棒或风扇进行加热或制冷操作。此外,该控制器还利用DS1302完成计时功能,在预设时间点…...

git-repo使用

即使用 XML 格式文件&#xff08;manifest 清单文件&#xff09;定义一个项目的多仓库关联&#xff0c;然后用 repo 客户端工具操作多仓库 git repo命令行格式&#xff1a; git repo <子命令> <参数>创建一个空目录&#xff0c;作为工作区。 $ mkdir workspace$ …...

如何设计实现完成一个FPGA项目

设计并完成一个FPGA项目是一个复杂但非常有价值的工程任务。以下是一个详细的步骤指南,帮助你从零开始完成一个FPGA项目。 1. 项目定义与需求分析 确定项目目标:明确项目要实现的功能和性能指标。需求分析:列出所有功能需求、性能需求、接口需求等。可行性分析:评估技术可…...

Oracle(106)如何实现透明数据加密?

透明数据加密&#xff08;TDE&#xff09;是一种用于保护数据库中静态数据的加密技术。TDE通过自动加密数据库文件和日志文件&#xff0c;确保数据在磁盘上是加密的&#xff0c;从而防止未经授权的访问。TDE的一个主要优点是它对应用程序是透明的&#xff0c;不需要对应用程序代…...

用Python实现时间序列模型实战——Day 18: 时间序列中的季节性与周期性预测

一、学习内容 1. 季节性调整与周期性预测 季节性调整 是在时间序列分析中常用的技术&#xff0c;旨在去除数据中因季节性波动导致的周期性变化&#xff0c;使数据更易于解释和预测。通常&#xff0c;我们可以使用季节性分解方法来分离时间序列中的趋势、季节性和随机成分。 …...

JavaScript ES6特性(var let const、function=>、增强表达赋值、类与对象)

一、var let const 1、var var明明定义在for里面的但是外部能够访问这个变量,说明var可以跨域访问。 2、let let明明定义在for里面的但是外部不能够访问这个变量,说明let不可以跨域访问。 3、const const foo = {}; // 为 foo 添加一个属性,可以成功 foo.prop = 123; fo…...

Paddle安装详解(CPU版本)

目录 1. 安装Python2. 安装paddle3. 验证3.1 初步验证3.2 将numpy版本从2.1.1降为2.0.13.3 再次验证1. 安装Python Python版本 C:\Users\james>python --version Python 3.12.62. 安装paddle 安装paddle及依赖库setuptools python -m pip install paddlepaddle==2.6.1 -…...

PHP即刻送达同城派送小程序系统

即刻送达&#xff0c;同城派送小程序系统让生活更便捷 &#x1f680; 瞬间连接&#xff0c;即刻送达的奇迹 你是否曾经因为等待快递而焦急万分&#xff1f;是否渴望有一种方式能让物品像魔法一样瞬间出现在你面前&#xff1f;现在&#xff0c;有了“即刻送达同城派送小程序系…...

RabbitMQ的Direct Exchange模式实现的消息发布案例

Producer生产者代码 import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory;public class RabbitMQProducer {private final static String EXCHANGE_NAME "direct_message_exchange";privat…...

数据结构-二叉树-基础知识

数据结构-二叉树-基础知识 1.树1.1什么是树1.2基本概念子节点、父节点叶节点节点的度树的高度/深度节点的子孙、祖先 1.3树与非树1.4如何实现1.5实例 2.二叉树2.1什么是二叉树2.2特殊的二叉树满二叉树完全二叉树 2.3性质层数度节点 2.4存储结构 1.树 1.1什么是树 树型结构是一…...

wangeditor——cdn引入的形式创建一个简易版编辑器——js技能提升

昨天同事那边有个需求&#xff0c;就是要实现聊天功能&#xff0c;需要用到一个富文本编辑器&#xff0c;参考如下&#xff1a; 上面的这个效果图是博客园的评论输入框 最终使用wangEditor编辑器实现的效果如下&#xff1a; 只保留了个别的菜单&#xff1a; 默认模式的wangE…...

9.11.

Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), speecher(new QTextToSpeech(this)) {//设置时钟ui->setupUi(this);startTimer(1000);//文本框label居中对齐ui->label_2->setAlignment(Qt::AlignCenter);connect(this,&Widget::my_sign…...

【GeekBand】C++设计模式笔记1_介绍

课程目标 理解松耦合设计思想掌握面向对象设计原则掌握重构技法改善设计掌握GOF核心设计模式 什么是设计模式 目标&#xff1a;复用&#xff0c;以不变应万变 GOF设计模式 从面向对象谈起 深入理解面向对象 向下&#xff1a;深入理解三大面向对象机制 封装&#xff1a;隐藏…...