写点什么

每位开发者都应该知道的 7 种 Node.js 设计模式

  • 2024-08-01
    北京
  • 本文字数:5603 字

    阅读完需:约 18 分钟

大小:1.67M时长:09:44
每位开发者都应该知道的7种 Node.js 设计模式

设计模式被用来解决日常软件开发中所遇到的设计问题。

 

这些问题可能包括:

  1. 维持数据库连接

  2. 创建和管理对象

  3. 向订阅了特定实体的一组用户发送通知

 

针对这些问题,如果你试图独自思考并设计出最优的解决方案,可能需要花费大量的精力。

 

但,其实你完全不必这样做!

 

设计模式正是为了解决这些反复出现的问题而产生的。因此,你所要做的就是根据你的框架和语言实现特定的模式就可以了!

 

那么,让我们来看一下在 Node.js 中,你可能需要实现的最常见的设计模式。

 

顺便说一下,如果您想跳过文章直接阅读代码,请查看我的 Bit Scope

 


门面模式

 


首先,重要的是要理解门面模式(Facade Pattern),因为它在 Node.js 应用中非常重要。

简单来说,门面模式就是通过提供统一的接口来简化复杂子系统的设计。

 

作为单一入口,它隐藏了所有的内部实现细节,简化了调用者与底层功能的交互。它就像一个网关,将客户端与复杂的细节隔离开来。

 

例如,使用 Google 账户登录网站的过程就可以视为门面模式的一个现实的例子。你只需要点击“使用 Google 登录” 按钮,而这个按钮就是一个统一的登录选项。

 

你无需再为输入邮箱、密码等其他个人信息而烦恼。

 

优势:

  1. 简化接口: 减少开发人员的认知负荷,使其与复杂系统的交互变得简单。

  2. 降低耦合性: 将客户端代码与内部实现细节解耦,提高代码的可维护性和灵活性。

  3. 提高可读性: 将复杂的逻辑封装在门面中,使代码更有条理且更易于理解。

  4. 受控访问: 可在访问底层功能之前通过设定特定的规则或校验实现访问控制。

 

思考如下代码:

 

// 复杂模块class ComplexModule {  initialize() {    // 复杂的初始化逻辑...  }  operation1() {    // 复杂操作1  }  operation2() {    // 复杂操作2  }}// 客户端代码const complexModule = new ComplexModule();complexModule.initialize();complexModule.operation1();complexModule.operation2();
复制代码

 

上述代码片段展示了客户端如何在模块外部与其子系统进行交互。你不仅需要手动执行所有操作,并且在维护代码时很可能会遇到问题。

 

再来看看下面这段代码:

 

// 在复杂模块中使用门面模式class ModuleFacade {  constructor() {    this.complexModule = new ComplexModule();  }  performOperations() {    this.complexModule.initialize();    this.complexModule.operation1();    this.complexModule.operation2();  }}// 客户端代码const moduleFacade = new ModuleFacade();moduleFacade.performOperations();
复制代码

 

现在,如你所见,我们不需要再在模块外部对子模块进行初始化,而是将其封装在 performOperations 函数中,它会负责处理所有与复杂内部子系统的通信。

 

这样,你就能以一种简洁的方法来处理复杂的通信树。

 

点击这里查看完整代码实现。

 

单例模式

 


接下来,是你在退休之前可能每天都要使用的模式之一。有时候,你需要确保某些东西只能有一个实例。

 

例如,考虑一下数据库连接。在特定时间内,应用程序是否需要一个以上的数据库连接?能否重用现有连接?

 

这就是单例模式的作用所在。它确保你的类只有一个全局实例,且可以通过静态方法进行访问。

 

优势:

  1. 全局访问:是一种在应用程序中任何位置访问共享数据和功能的便捷方式。

  2. 资源管理:通过单一实例来确保诸如数据库连接、日志记录器或文件句柄等资源的高效使用。

  3. 一致性:使更改仅影响单个实例,确保行为执行的一致性。

  4. 受控状态:通过集中管理数据操作来简化状态管理。

 

下面是在 Node.js 中实现单例的一个示例:

 

class ConfigManager {  constructor() {    this.databaseConfig = { /* 数据库配置 */ };    this.apiKey = "your_api_key";    // 其他应用配置  }

static getInstance() { if (!this.instance) { this.instance = new ConfigManager(); } return this.instance; }

getDatabaseConfig() { return this.databaseConfig; }

getApiKey() { return this.apiKey; } // 其他获取配置的方法}

// 客户端const configManager = ConfigManager.getInstance();

// 访问配置const databaseConfig = configManager.getDatabaseConfig();const apiKey = configManager.getApiKey();
复制代码

 

你可能有一个或多个与外部服务交互的 Node.js 应用程序,每个服务都需要特定的配置参数。使用单例模式,你可以通过创建一个 ConfigManager 类来负责集中处理这些配置。

 

点击这里查看完整代码实现。

 

适配器模式

 


接下来,你需要设想一个场景,即你正在使用的 API 和你正在开发的客户端之间存在 API 不兼容的问题。

 

例如,你可能有一个需要两个 props 的 React 组件:

 

  1. 名字

  2. 姓氏

 

但你的 API 只会返回一个变量

 

  1. 全名

 

因此,如果你没有调整 API 返回体的权限,就必须利用现有的资源使应用能够正常运行。

 

这就是适配器模式的作用所在。

 

适配器模式可以在不兼容的接口之间架起桥梁,从而使它们能够无缝地协同工作。

 

优势:

  1. 互操作性:使具有不同接口的组件之间能够通信,促进系统集成和重用。

  2. 松散耦合:将客户端代码与适配组件的具体实现解耦,提高灵活性和可维护性。

  3. 灵活性:允许通过创建新的适配器来适应新组件,而无需修改现有代码。

  4. 可重用性:适配器实现可以针对类似的兼容性需求重复使用,减少代码重复。

 

示例:

 

下面是适配器设计模式的一个简单的代码示例。

 

点击这里查看完整代码实现。

 

老系统

 

class OldSystem {  request() {    return "Old System Request";  }}
复制代码

 

新系统和适配器

 

class NewSystem {  newRequest() {    return "New System Request";  }}

class Adapter { constructor(newSystem) { this.newSystem = newSystem; }

request() { return this.newSystem.newRequest(); }}
复制代码

 

客户端使用

 

// 老系统的使用const oldSystem = new OldSystem();console.log(oldSystem.request()); // 输出:Old System Request

// 通过适配器使用新系统const newSystem = new NewSystem();const adapter = new Adapter(newSystem);console.log(adapter.request()); // 输出: New System Request
复制代码

 

构造器模式

 


接下来,我们将介绍的模式可以用来构建对象,从而使对象的管理变得更加容易。

 

构建器模式将一个复杂对象的构建与它的表示分离。

 

这就像组装一台定制电脑——单独选择组件并构建最终产品。在 Node.js 中,构造器模式有助于构建具有复杂配置的对象,并保证这个过程可以分步进行且可定制。

 

在这种设计模式中,你可以为对象的每个可选属性创建单独的方法(“构造器”),而不是创建一个带有大量参数的构造函数。这些方法通常会返回类的当前实例(this),将它们串联起来就可以逐步构建出对象。

 

优势:

  1. 提高可读性:使用有意义的方法名显式设置每个属性,使代码更加清晰。

  2. 灵活性:仅使用必要的属性来构建对象,避免未使用的字段出现意料之外的值。

  3. 不可变性:build()方法通常会创建一个新实例而不是修改构造器,这增强了不可变性,简化了推理过程。

  4. 错误处理:在构造器方法中验证属性值并抛出错误比在复杂的构造函数中更容易。

 

示例:

 

下面是构建器设计模式的一个简单的代码示例。

 

点击这里查看完整代码实现。

 

class UserBuilder {  constructor(name) {    this.name = name;    this.email = null;    this.address = null;  }

withEmail(email) { this.email = email; return this; // 通过返回 this 实现方法的链式调用 }

withAddress(address) { this.address = address; return this; }

build() { // 验证和构造用户对象 const user = new User({ name: this.name, email: this.email, address: this.address, }); return user; }}

// 客户端代码const user1 = new UserBuilder('John') .withEmail('john@example.com') .withAddress('123 Main St.') .build();

console.log(user1); // 打印完整的用户对象的值
复制代码

 

工厂模式

 


工厂模式为对象创建提供了一个接口,但允许子类改变所创建对象的类型。

 

把它想象成一个制造工厂,不同的装配线生产不同的产品。在 Node.js 中,工厂模式在创建对象时无需指定其具体类,提高了灵活性和可扩展性。

 

优势:

  1. 解耦: 客户端代码与特定对象的创建逻辑解耦,提高了代码的灵活性和可维护性。

  2. 集中控制: 开发者可以轻松地添加新对象类型或修改现有的对象类型,只需在工厂中处理更改,而不会影响客户端代码。

  3. 灵活性: 工厂可根据运行时条件或配置选择合适的对象,使代码更具适应性。

  4. 封装性: 对象创建的细节被隐藏在工厂内部,提高了代码的可读性和可维护性。

 

示例:

下面是工厂设计模式的一个简单的代码示例。

 

点击这里查看完整代码实现。

 

图形接口

 

// Shape接口class Shape {  draw() {}}
复制代码

 

多种图形

 

// Shape接口的具体实现class Circle extends Shape {  draw() {    console.log("Drawing Circle");  }}class Square extends Shape {  draw() {    console.log("Drawing Square");  }}class Triangle extends Shape {  draw() {    console.log("Drawing Triangle");  }}
复制代码

 

图形工厂

 

// ShapeFactory类负责创Shape的实例class ShapeFactory {  createShape(type) {    switch (type) {      case 'circle':        return new Circle();      case 'square':        return new Square();      case 'triangle':        return new Triangle();      default:        throw new Error('Invalid shape type');    }  }}
复制代码

 

客户端代码

 

// 客户端代码使用ShapeFactory来创建Shapeconst shapeFactory = new ShapeFactory();const circle = shapeFactory.createShape('circle');circle.draw(); // 输出:画圆const square = shapeFactory.createShape('square');square.draw(); // 输出:画正方形const triangle = shapeFactory.createShape('triangle');triangle.draw(); // 输出:画三角形
复制代码

 

原型模式

 


原型模式通过复制一个已存在的对象(称为原型)来创建新对象。

 

通过该模式可以创建主对象的副本。当创建对象的成本比复制该对象的成本高时,它就非常有用。

 

概念:

  1. 原型:定义一个具有所需属性和方法的基准对象。该对象将作为后续对象的蓝图。

  2. 克隆:通过复制原型来创建新对象,通常使用如 Object.create 之类的内置方法或自定义克隆逻辑。

  3. 定制:新创建的对象可以修改各自的属性,但不会影响原始原型。

 

优势:

  1. 性能:克隆现有对象通常比从头开始构建新对象更快,复杂对象尤为明显。

  2. 内存效率:通过原型共享属性和方法,可以避免冗余存储,减少内存使用。

  3. 动态修改:开发者可以轻松地扩展原型,为当前和未来的所有实例添加新功能。

 

下面是原型模式的一个简单的代码示例。

 

点击这里查看完整代码实现。

 

原型对象

 

// 原型对象const animalPrototype = {  type: 'unknown',  makeSound: function () {    console.log('Some generic sound');  },  clone: function () {    return Object.create(this); // 使用Object.create()进行克隆  },};
复制代码

 

自定义实现

 

// 基于原型的自定义实例const dog = animalPrototype.clone();dog.type = 'Dog';dog.makeSound = function () {  console.log('Woof!');};

const cat = animalPrototype.clone();cat.type = 'Cat';cat.makeSound = function () { console.log('Meow!');};
复制代码

 

客户端代码

 

// 使用自定义实例的客户端代码dog.makeSound(); // 输出:Woof!cat.makeSound(); // 输出:Meow!
复制代码

 

代理模式

 


代理模式是通过充当另一个对象的代理或占位符,以实现对该对象的访问控制。

 

该模式就是在客户端和真实对象之间创建一个中介对象(“代理”)。这个代理控制着对真实对象的访问,可以在操作到达目标对象之前或之后实施拦截和修改。

 

这样,你就可以在不更改真实对象实现的前提下,添加额外的功能。代理模式对于懒加载、访问控制、添加日志以及调试功能等都非常有用。

 

为了更好地理解代理设计模式(Proxy Design Pattern)及其在 React 环境中的用例,可以看下这篇文章:React代理设计模式实战

 

优势

  1. 控制访问:在与真实对象交互之前强制执行权限或验证。

  2. 附加功能:添加日志记录、缓存或安全等特性,而无需更改对象本身。

  3. 抽象:通过隐藏真实对象的实现细节来简化客户端代码。

  4. 灵活性:在运行时动态更改目标对象或处理器的行为。

 

示例:

 

下面是该模式的一个简单示例,点击这里查看完整实现。

 

在所有这些示例中,我都通过 JavaScript Proxy 对象来为其他对象创建代理。要更深入地了解 JavaScript 内置代理,请访问此处

 

const target = {  name: 'Alice',  sayHello() {    console.log(Hello, my name is ${this.name} );  },};const handler = {  get(target, prop, receiver) {    console.log(Property ${prop} accessed );    return Reflect.get(target, prop, receiver);  },  set(target, prop, value, receiver) {    console.log(Property ${prop} set to ${value} );    return Reflect.set(target, prop, value, receiver);  },};const proxy = new Proxy(target, handler);proxy.name; // 输出:访问属性名proxy.sayHello(); // 输出:访问属性方法sayHello                  //        Hello, my name is Aliceproxy.name = 'Bob'; // 输出:属性名设置为Bob
复制代码

 

结束语

 

对于每个开发者来说,设计模式都是重要的学习内容。无论你是初学者还是专家,理解设计模式及其在现代软件开发中的应用都很重要,因为它能让你更快地构建出更好的软件。

 

如果你想查看本文涉及的代码,请查看我在 Bit Scope 中的实现。

 

感谢阅读!

 

原文地址:https://blog.bitsrc.io/nodejs-design-patterns-must-know-8ef0a73b3339

2024-08-01 12:307967

评论 2 条评论

发布
用户头像
写了一堆java的糟粕,除非你知道为什么,否则永远不要用class. 要我说唯一的设计模式就是function
2024-08-09 10:46 · 广东
回复
用户头像
很好
2024-08-02 07:21 · 广东
回复
没有更多了

外包学生管理系统的架构设计

面向对象的猫

私域流量系统开发,APP源码搭建

获客I3O6O643Z97

私域流量 抖音霸屏

带你认识MindSpore量子机器学习库MindQuantum

华为云开发者联盟

mindspore 量子机器 MindQuantum 量子机器学习库

小透明学弟的华为上岸之路

程序员鱼皮

Java c++ Python 大前端 后端

如何基于磁盘 KV 实现 Bitmap

Kvrocks

redis BitMap storage KV存储引擎

论文解读丨Zero-Shot场景下的信息结构化提取

华为云开发者联盟

测试 图网络 信息结构化 图卷积网络 zero-shot

给新手学习MySQL的建议

Simon

MySQL

一例智能网卡(mellanox)的网卡故障分析

安第斯智能云

后端

财务或类财务系统数值精度设计

路边水果摊

数字 财务 精度 数值

哔哩哔哩B站视频下载器推荐(简单又好用)

资源君

工具 分享 哔哩哔哩 b站视频下载 教程分享

SpringBoot启动加载监听器以及监听应用启动阶段事件

捡对象的cy

springboot

手把手教你实现Android编译期注解

vivo互联网技术

android 注解 sdk

我的职场规划

escray

学习 极客时间 朱赟的技术管理课 7月日更

英特尔加速制程工艺和封装技术创新

E科讯

经典译文 | 项目经理必读的基本工作法则

LigaAI

项目管理 项目经理

电脑里的视频被误删了可以用EasyRecovery恢复吗?

淋雨

EasyRecovery 文件恢复 硬盘数据恢复

音视频时代你还不会NDK开发?详细的Android学习指南

欢喜学安卓

android 程序员 面试 移动开发

音视频时代你还不会NDK开发?看完必懂

欢喜学安卓

android 程序员 面试 移动开发

国际视频质量评估算法竞赛:火山引擎成功夺冠

抖音获客系统开发

获客I3O6O643Z97

抖音 头条抖音 抖音霸屏

图解红黑树

Ayue、

数据结构

以1敌10不是梦,Spring Boot企业级真实应用案例

博文视点Broadview

CWE发布2021年最危险的25种软件缺陷

华为云开发者联盟

安全 隐私保护 安全漏洞 cwe 软件缺陷

短视频获客软件系统开发公司

Python OpenCV Canny 边缘检测知识补充

梦想橡皮擦

Python 7月日更

Cypress 自动化测试

admin

自动化测试 Cypress 测试 单元测试 UI测试

来自网络资源资产管理的灵魂拷问

鲸品堂

网络 资源 运营商

开源 Rainbond 5.3.2 版本发布,易用的云原生应用管理平台

Barnett

云计算 开源 基础软件 小版本升级

一个Android程序员的腾讯面试心得,进阶学习资料!

欢喜学安卓

android 面试 移动开发 程序猿

科技监管能源运作?智慧能源从光热发电技术开始描述

一只数据鲸鱼

数据可视化 智慧能源 光热发电

最壕逆天改命:18名Java程序员凭阿里P8笔记,同时斩获一线大厂offer

Java架构师迁哥

每位开发者都应该知道的7种 Node.js 设计模式_架构/框架_Danusha Navod_InfoQ精选文章