4月10-12日 QCon 北京,与全球 140+ 顶尖工程师共同解构 AI 时代的技术浪潮! 了解详情
写点什么

每位开发者都应该知道的 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:308347

评论 2 条评论

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

注意 ! !|95% 的应用程序中发现错误配置和漏洞

SEAL安全

配置管理 软件供应链安全 漏洞管理

react源码中的协调与调度

flyzz177

React

CQRS与Event Sourcing

胖子笑西风

架构 DDD CQRS Event Sourcing #java

AntDB入选《2022爱分析·信创厂商全景报告》

亚信AntDB数据库

AntDB 信创 国产数据库 aisware antdb AntDB数据库

掌握这些前端手写面试题能进大厂吗

helloworld1024fd

JavaScript

Fiori Elements 应用进行二次开发的一个具体案例分享

汪子熙

SAP Fiori ui5 Web应用 11月月更

「Go易错集锦」如何正确设置枚举中的零值

Go学堂

golang 程序员 个人成长 枚举 11月月更

react源码中的hooks

flyzz177

React

年搜索量超7亿次背后:这款APP用火山引擎 DataTester 完成“数据驱动”

字节跳动数据平台

大数据 数据分析 A/B测试

《全国一体化政务大数据体系建设指南》发布,隐私计算将如何发挥作用?

洞见科技

高频js手写题之实现数组扁平化、深拷贝、总线模式

helloworld1024fd

JavaScript

PGL图学习之图神经网络GNN模型GCN、GAT[系列六]

汀丶人工智能

图神经网络 11月月更

【专项测试系列】-缓存击穿、穿透、雪崩专项测试

京东科技开发者

缓存 测试 缓存穿透 缓存击穿 缓存雪崩

谈谈前端应用里图标(Icon)的渲染和内容提取方式

汪子熙

前端开发 SAP ui5 Web应用 11月月更

云安全系列3:如何构建云安全策略

HummerCloud

云计算 数据安全 云安全 11月月更

详解Native Memory Tracking之追踪区域分析

华为云开发者联盟

开发 内存 华为云

Go类型转换和类型断言可别搞混了

王中阳Go

golang 高效工作 学习方法 面试题 11月月更

《关键信息基础设施安全保护要求》于明年五月正式实施

行云管家

网络安全

DDD与应用架构

胖子笑西风

架构 DDD 框架 整洁架构 Java core

SpringBoot 接口层统一加密解密

小小怪下士

Java 程序员 springboot

面试官:请实现Javascript发布-订阅模式

helloworld1024fd

JavaScript

浅谈深度学习中的概率

华为云开发者联盟

人工智能 华为云

阿里Redis最全面试全攻略,读完这个就可以和阿里面试官好好聊聊

钟奕礼

Java java程序员 java面试 java编程

全国独家 | 上海线下面授大规模敏捷LeSS认证 | 2022年12月8-10日

ShineScrum

less 大规模敏捷 LeSS认证 吕毅老师

阿里云架构师张先国:揭秘ECS倚天实例背后的技术

云布道师

算力 云栖大会 倚天实例

react源码中的fiber架构

flyzz177

React

vue为什么v-for的优先级比v-if的高?

bb_xiaxia1998

Vue

前端面试被问到的js手写面试题汇总

helloworld1024fd

JavaScript

GaussDB CN服务异常实例分析

华为云开发者联盟

数据库 华为云 GaussDB

记一场vue面试

bb_xiaxia1998

Vue

Kata3.0.0 x LifseaOS x 龙蜥内核三管齐下!带你体验最新的安全容器之旅

OpenAnolis小助手

容器 云原生 内核 龙蜥社区 袋鼠RunD

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