NodeJS 中的设计模式

Node.js 是一个流行的 JavaScript 运行时,允许开发者使用事件驱动、非阻塞 I/O 模型构建可扩展的网络应用程序。和任何复杂的框架一样,Node.js 应用程序可以从使用成熟的设计模式中受益,以促进代码重用、可维护性和健壮性。本文将概述一些对 Node.js 开发非常有用的设计模式。

设计模式简介

设计模式是软件开发人员在编码过程中经常遇到的问题的经过验证的解决方案。它们为解决挑战提供了结构化的方法,并促进了软件架构中的最佳实践。通过整合设计模式,开发者可以创建更健壮、可维护和可扩展的代码库。

为什么设计模式在 Node.js 中很重要

Node.js 以其非阻塞事件驱动的架构而闻名,在软件设计中提出了独特的挑战和机遇。应用针对 Node.js 的设计模式可以导致更高效、更优化的应用程序。让我们探讨一些在 Node.js 生态系统中特别有价值的关键设计模式:

单例模式

单例模式确保类只有一个实例,并为其提供一个全局访问点。在 Node.js 中,模块可以被缓存并在应用程序中共享,使用单例模式可以有效地管理资源。例如,可以将数据库连接池实现为单例,以防止资源浪费。

class Database {constructor() {this.connection = null;}static getInstance() {if (!Database.instance) {Database.instance = new Database();}return Database.instance;}connect() {// 连接到数据库this.connection = 'Connected';}
}const db1 = Database.getInstance();
const db2 = Database.getInstance();console.log(db1 === db2); // truedb1.connect();console.log(db1.connection); // 'Connected'
console.log(db2.connection); // 'Connected'

关键点是:

  • 构造函数被设为私有以防止直接实例化。
  • 静态方法 getInstance() 如果实例尚不存在,则创建一个并返回。这确保只创建一个实例。
  • 实例 db1db2 指向同一个对象。
  • db1 连接时,db2 也会获得连接,因为它们是同一个对象。

这确保只有一个数据库实例,并防止连接重复。单例模式适用于只应存在一个类实例的情况。

工厂模式

工厂模式提供了一种创建对象的方法,而无需指定将创建的对象的确切类。在 Node.js 上下文中,这可以简化对象创建,特别是在处理诸如读取文件或进行 API 调用等异步操作时。通过抽象对象创建,工厂模式增强了代码的可读性和可重用性。

class Car {constructor(model, price) {this.model = model;this.price = price;}
}class CarFactory {createCar(model) {switch(model) {case 'civic':return new Car('Honda Civic', 20000);case 'accord':return new Car('Honda Accord', 25000);case 'odyssey':return new Car('Honda Odyssey', 30000);default:throw new Error('Unknown model');}}
}const factory = new CarFactory();const civic = factory.createCar('civic');
const accord = factory.createCar('accord');console.log(civic.model); // Honda Civic
console.log(accord.model); // Honda Accord

关键点是:

  • CarFactory 类处理对象创建逻辑。
  • createCar() 方法根据型号返回一个 Car 实例。
  • 客户端代码使用工厂而不是直接构造函数调用。

这样抽象了对象创建逻辑,并允许轻松扩展支持的型号。工厂模式在存在不应与客户端代码耦合的复杂对象创建逻辑时非常有用。

观察者模式

Node.js 的事件驱动特性与观察者模式非常契合。该模式涉及维护依赖项列表(称为观察者)并通知它们任何状态更改的主体。在 Node.js 上下文中,这可以用来构建事件驱动系统,例如实时应用程序和聊天应用程序。

class Subject {constructor() {this.observers = [];}subscribe(observer) {this.observers.push(observer);}unsubscribe(observer) {this.observers = this.observers.filter(o => o !== observer);}notify(data) {this.observers.forEach(o => o.update(data));}
}class Observer {constructor(name) {this.name = name;}update(data) {console.log(`${this.name} received ${data}`);}
}const subject = new Subject();const observer1 = new Observer('观察者 1');
const observer2 = new Observer('观察者 2');subject.subscribe(observer1);
subject.subscribe(observer2);subject.notify('你好,世界');
// 观察者 1 received 你好,世界
// 观察者 2 received 你好,世界subject.unsubscribe(observer2);subject.notify('再见,世界');
// 观察者 1 received 再见,世界

关键点是:

  • Subject 维护一个观察者列表。
  • 观察者通过 subscribeunsubscribe 订阅和取消订阅主体。
  • 当调用 notify() 时,主体会更新所有已订阅的观察者。

这样可以将更新发布给多个对象,而不将发布者与订阅者耦合在一起。观察者模式对于事件处理和异步工作流程非常有用。

中间件模式

Node.js 的中间件架构广泛用于处理 Web 应用程序中的请求和响应。中间件模式涉及一系列按顺序处理请求的函数。每个函数都可以在将请求或响应传递给链中的下一个函数之前修改它。这种模式增强了模块性,并允许开发者插入各种功能而不会将它们紧密耦合。

const express = require('express');
const app = express();const logger = (req, res, next) => {console.log('已记录');next();
}const authenticate = (req, res, next) => {// 认证用户next();
}app.use(logger);
app.use(authenticate);app.get('/', (req, res) => {res.send('你好,世界');
});app.listen(3000);

关键点是:

  • 中间件函数 loggerauthenticate 包装路由处理程序。
  • 它们可以在路由前后执行逻辑。
  • next() 函数将控制传递给下一个中间件。
  • app.use() 在全局范围内挂载中间件。

这允许将请求处理分解为更小的可重用单元。中间件模式在 Express 和其他 Node.js 框架中非常常见,用于日志记录、身份验证等方面。

一些其他的中间件示例包括解析器、压缩、速率限制等。该模式允许以模块化方式构建请求管道。

模块模式

模块模式是 Node.js 中最基本但也是最基本的模式之一。它允许您将代码组织成单独的文件或模块,封装特定功能。

// counter.jslet count = 0;const increment = () => {count++;
}const decrement = () => {count--;
}const get = () => {return count;
}module.exports = {increment,decrement,get
};// app.jsconst counter = require('./counter');counter.increment();
counter.increment();console.log(counter.get()); // 2counter.decrement();console.log(counter.get()); // 1

关键点是:

  • 模块 counter.js 导出一些操作私有 count 变量的函数。
  • 这些函数在模块内部封装了逻辑和数据。
  • app.js 导入模块并使用公共 API。

这种模式提供了数据封装,并且只暴露了公共 API。模块模式在 Node.js 中非常常见,用于组织代码成可重用和可移植的模块。

其他一些示例包括中间件模块、实用程序库、数据访问层等。该模式有助于管理依赖关系并隐藏实现细节。

装饰器模式

装饰器动态地向对象添加新功能,而不影响其他实例。这非常适合在 Node 中扩展核心模块。

class Car {constructor() {this.price = 10000;}getPrice() {return this.price;}
}class CarOptions {constructor(car) {this.car = car;}addGPS() {this.car.price += 500;}addRims() {this.car.price += 300;}
}const basicCar = new Car();console.log(basicCar.getPrice()); // 10000const carWithOptions = new CarOptions(basicCar);carWithOptions.addGPS();
carWithOptions.addRims();console.log(carWithOptions.car.getPrice()); // 10800

关键点是:

  • CarOptions 包装了 Car 类并扩展了其行为。
  • 类似 addGPS() 的方法修改了包装的 Car 的状态。
  • 客户端具有带有附加功能的装饰后的 Car 实例。

这样可以在运行时动态扩展行为。装饰器模式对于抽象化和不必要地子类化以添加小功能非常有用。

一些其他的例子包括身份验证路由、日志包装器、缓存装饰器等。该模式提供了一种灵活的方式,以在 Node.js 应用程序中遵循开放/封闭原则。

依赖注入模式

依赖注入是一种模式,其中模块或类从外部来源接收依赖项,而不是在内部创建它们。它有助于解耦、测试和可重用性。

// service.js
class Service {constructor(db, logger) {this.db = db;this.logger = logger;}async getUser(userId) {const user = await this.db.findUserById(userId);this.logger.log(`Fetched user ${user.name}`);return user;}
}// app.js
const Database = require('./database');
const Logger = require('./logger');const db = new Database();
const logger = new Logger();const service = new Service(db, logger);service.getUser(1);

关键点是:

  • Service 类通过构造函数声明依赖项。
  • 调用代码注入了实际的依赖项,比如 dblogger
  • 这将 Service 与具体的依赖项解耦。

好处:

  • 模块之间的松耦合
  • 通过模拟依赖项进行更轻松的测试
  • 能够交换实现

依赖注入模式通常与 Node.js 框架(如 NestJS)一起使用。它能够更好地组织代码并提高可重用性。

Promise 模式

Promise 是 Node.js 中用于异步编程的一种模式。它们代表异步操作的最终结果。下面是一个简单的例子:

const fetchData = new Promise((resolve, reject) => {// 异步操作const data = getDataFromDatabase();if (data) {resolve(data);} else {reject('获取数据时出错');}
});fetchData.then(data => {// 处理成功的数据}).catch(err => {// 处理错误});

关键点是:

  • Promise 接受一个带有 resolvereject 函数的回调。
  • 异步操作在回调内开始。
  • resolve(data) 在成功时返回数据。
  • reject(error) 在失败时返回错误。
  • 消费者使用 .then().catch() 来获取结果。

好处:

  • 处理异步结果的标准化方式
  • 能够链式和组合 Promise

Promise 对于现代 Node.js 开发至关重要,并且支持诸如 axiosfs.promises 等核心 API ,可以用来编写干净、整洁的异步代码。

应用设计模式

既然我们已经探讨了一些与 Node.js 的优势相吻合的关键设计模式,现在让我们深入了解如何有效地使用它们:

1. 理解上下文

在应用任何设计模式之前,了解应用程序的上下文非常重要。考虑诸如应用程序的需求、可扩展性需求以及您试图解决的具体挑战等因素。设计模式并不是一种适合所有场景的解决方案;它们应该根据项目的独特特性进行定制。

2. 模块化

Node.js 通过其模块系统鼓励模块化。在实现设计模式时,努力保持模块小型、专注和单一职责。这促进了代码的可重用性和可维护性,使得可以轻松地替换或增强特定功能,而不影响整个应用程序。

3. 异步模式

鉴于 Node.js 的异步特性,选择与异步编程范式相一致的设计模式至关重要。观察者模式和中间件模式等模式自然地适应了异步环境,允许开发者无缝地处理事件和异步操作。

总结

设计模式使 Node.js 开发者能够编写组织良好、灵活且健壮的代码。利用诸如工厂模式、装饰器模式和单例模式等经过验证的模式,使您能够构建易于维护和扩展的大型应用程序。理解如何应用设计原则对于掌握高级 Node 开发至关重要。

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

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

相关文章

在微信小程序中或UniApp中自定义tabbar实现毛玻璃高斯模糊效果

backdrop-filter: blur(10px); 这一行代码表示将背景进行模糊处理,模糊程度为10像素。这会导致背景内容在这个元素后面呈现模糊效果。 background-color: rgb(255 255 255 / .32); 这一行代码表示设置元素的背景颜色为白色(RGB值为0, 0, 0)&a…

docker批量删除容器或镜像

删除容器 停止所有容器 删除所有容器,需要先停止所有运行中的容器 docker stop docker ps -a -q docker ps -a -q,意思是列出所有容器(包括未运行的),只显示容器编号,其中 -a : 显示所有的容器,包括未运行的。 …

以太坊的演变:EIP、ERC 概念以及革命性的 ERC20、ERC721 和 ERC115 标准

文章目录 一、EIP——以太坊发展的基石1.1 什么是EIP?1.2 历史背景:前身的 BIP1.3 EIP的重要性1.4 流程:从提案到实施 二、进入 ERC——以太坊内的标准化协议2.1 解读 ERC:以太坊征求意见2.2 ERC 标准的诞生和意义 三、聚焦 ERC20…

如何动态修改spring中定时任务的调度策略(2)

上一篇文章中我们一下走读了一下spring中实现@Scheduled的源码,想必你对spring中实现定时调度的原理更加了解吧,文末我们两个问题, 1.spring在进行定时调度时,使用的线程池是默认,那么这个默认的线程池的配置是怎样的呢? 2.如何动态调整调度策略。 接下来我们详细分析一…

第四百一十二回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 示例代码4. 内容总结 我们在上一章回中介绍了"给geolocator插件提交问题的结果"相关的内容,本章回中将介绍自定义标题栏.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我…

在Linux/Ubuntu/Debian中使用iFuse访问iOS 设备(例如 iPhone 或 iPad)上的文件可双向复制粘贴

iFuse 是一款工具,可让你在 Linux 系统上安装 iOS 设备(例如 iPhone 或 iPad),使你能够访问其文件系统并与设备传输文件。 以下是有关如何使用 iFuse 的基本指南: 安装依赖项:在安装 iFuse 之前&#xff0c…

go语言-基础元素与结构的使用

go基础元素与结构的使用,快速上手 编译go文件 编译为可执行文件 go build 文件名.go运行文件 ./文件名输入/输出 引用fmt库(关于输入输出的库) 输入 **scanf:**按照给定的格式依次读取数据(包括非法数据&#xff…

计算机网络——HTTP

HTTP报文格式长什么样? HTTP报文它是由请求行,请求头,请求体组成的 请求行他是请求或响应的基本信息 请求头他是使用key-value格式可以更加详细的说明报文 请求体是传输的具体内容 是如何分割的? 请求行与请求头他是用\r\n来…

wireshark windows 抓包https

windows下 1.配置环境变量以生成ssl协商会话密钥日志记录 系统设置-》高级设置-》环境变量 新增环境变量 SSLKEYLOGFILE C:\Users\Public\Documents\SSLKEY\sslkey.log 打开公用共享文档创建SSLKEY文件夹用于后续系统存放协商密钥日志 2.配置Wireshark选项进行抓包 点击…

(一)Linux+Windows下安装ffmpeg

一丶前言 FFmpeg是一个开源的音视频处理工具集,由多个命令行工具组成。它可以在跨平台的环境中处理、转换、编辑和流媒体处理音视频文件。 FFmpeg支持多种常见的音视频格式和编解码器,可以对音视频文件进行编码、解码、转码、剪辑、合并等操作。它具有广…

最近火绒的explorer问题,电脑黑屏只有鼠标

由于安全限制,覆盖文件是行不通的,按照火绒官方给的方法试试,还是不行。主要是他最后一步写得有问题。恭喜火绒,成功的将我们所有客户的电脑安装的火绒卸载。 解决方案 1、CTRLSHIFTESC调出任务管理器; 2、左上角&am…

.net使用excel的cells对象没有value方法——学习.net的Excel工作表问题

$exception {"Public member Value on type Range not found."} System.MissingMemberException 代码准备运行问题解决1. 下载别的版本的.net框架2. 安装3. 运行 代码 Imports Excel Microsoft.office.Interop.Excel Public Class Form1Private Sub Button1_Click(…

「实战应用」如何用DHTMLX构建自定义JavaScript甘特图(二)

DHTMLX Gantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的所有需求,是最完善的甘特图图表库。 当您声称您的产品具有高级定制功能时,客户一定会对产品进行严格测试,这个规则当然适用于DHTMLX Gantt&…

万用表革新升级,WT588F02BP-14S语音芯片助力智能测量新体验v

万能表功能: 万能表是一款集多功能于一体的电子测量工具,能够精准测量电压、电流、电阻等参数,广泛应用于电气、电子、通信等领域。其操作简便、测量准确,是工程师们进行电路调试、故障排查的得力助手,为提升工作效率…

Rust 中Self 关键字的两种不同用法

在 Rust 中,Self 是一个特殊的类型标识符,它代表了当前结构体或枚举类型。在结构体或枚举类型的定义中,Self 可以用于表示该类型的任何地方,包括方法签名、构造函数、类型别名等。 构造函数中的 Self: 在这段代码中&a…

大语言模型数据集alpaca羊驼数据集、Vicuna骆马数据集异同、作用、使用领域

文章目录 大语言模型数据集alpaca羊驼数据集、Vicuna骆马数据集异同、作用、使用领域Alpaca和Vicuna简介AlpacaVicuna相同点不同点 alpaca、vicuna能否用在大语言模型微调中?alpaca、vicuna进行大语言模型微调时,由于其已经是标准化数据集,还…

奥特曼剧透GPT-5,将在高级推理功能上实现重大进步

奥特曼:“GPT-5的能力提升幅度将超乎人们的想象...” 自 Claude 3 发布以来,外界对 GPT-5 的期待越来越强。毕竟Claude 3已经全面超越了 GPT-4,成为迄今为止最强大模型。 而且距离 GPT-4 发布已经过去了整整一年时间,2023年3月1…

2024年区块链、电子信息与计算机工程国际会议(ICBEICE 2024)

2024年区块链、电子信息与计算机工程国际会议(ICBEICE 2024) 2024 International Conference on Blockchain, Electronic Information and Computer Engineering 会议简介: 2024年区块链、电子信息与计算机工程国际会议(ICBEIC…

Android 源码中 内置系统App(整个APP源码方式集成)

1. 如何新建一个系统 App 项目 使用 Android Studio 新建一个空项目 FirstSystemApp,包名设置为 com.yuandaima.firstsystemapp,语言选择 Java。后面为叙述方便称该项目为 as 项目。 接着在 jelly/rice14 目录下创建如下的目录和文件: 接着…

安卓面试题多线程 96-100

96. 简述notify()和notifyAll()有什么区别 ?notify可能会导致死锁,而notifyAll则不会任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 中的代码 使用notifyall,可以唤醒 所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一…