写点什么

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

评论 2 条评论

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

"三高"Mysql - Mysql的基础结构了解

懒时小窝

MySQL 数据库

CorelDRAW2022下载及新增功能讲解

茶色酒

cdr2022

重新开始学习测试驱动开发

escray

学习笔记 测试驱动开发

《软件开发的201个原则》思考:3.开发效率和质量密不可分

非晓为骁

程序员 个人成长 软件工程 软件开发原则 开发质量

10个Python set 常用操作函数!,oppoPython面试题

程序媛可鸥

Python 程序员 面试

2022美赛单变量深度学习LSTM 时间序列分析预测,作为Python开发者

程序媛可鸥

Python 程序员 面试

28,2021最新Python面试笔试题目分享

程序媛可鸥

Python 程序员 面试

架构实战营 毕业设计项目

樰巳-堕~Horry

架构实战营 「架构实战营」

Apple任意代码执行漏洞,为了跳槽强刷1000道Python真题

程序媛可鸥

Python 程序员 面试

架构实战营模块九-毕业设计-电商秒杀系统

Jude

架构实战营

人工智能1秒检测一辆车,TA助力广本新车质量排名第一

百度大脑

Top Trending Libraries of 2021,PaddleOCR再开源8大前沿顶会论文模型!

百度大脑

开学季 | 飞桨AI Studio课程学习,小白也可以成为一名优秀的算法工程师

百度大脑

17个新手常见错误,送给初学Python的你!,憋个大招

程序媛可鸥

Python 程序员 面试

Axios 教程:Vue + Axios 安装及实战 - 手把手教你搭建加密币实时价格看板

蒋川

Vue Node axios

又一重量级国赛来啦,保研可加分 | 中国软件杯飞桨遥感赛道正式启动

百度大脑

#yyds内容盘点# 一文带你搞懂Python中变量与常量,Python开发框架

程序媛可鸥

Python 程序员 面试

30余种加密编码类型的密文特征分析,差点挂在第四面

程序媛可鸥

Python 程序员 面试

Java 中的静态字段和静态方法

踏雪痕

Java 3月程序媛福利 3月月更

4万字【Python高级编程】保姆式教学,330页PDF10万字的知识点总结

程序媛可鸥

Python 程序员 面试

90后,要有多少存款才正常?答案太扎心了,阿里P8大佬整理

程序媛可鸥

Python 程序员 面试

golang里的一些奇奇怪怪的东西

不登山的小鲁

golang Go 语言

06 - vulhub - Apache HTTPD 多后缀解析漏洞,2021年Python大厂面试分享

程序媛可鸥

Python 程序员 面试

Girlfriend含苞待笑——一次性处理上百份文档,Python开发实战讲解

程序媛可鸥

Python 程序员 面试

k8s组件的梳理(1),Python篇

程序媛可鸥

Python 程序员 面试

k8s组件的梳理,Glide的缓存机制

程序媛可鸥

Python 程序员 面试

实用机器学习笔记二十九:NLP 中的微调

打工人!

机器学习 学习笔记 nlp 机器学习算法 3月月更

软件入门之《编程指南》-学习路径和经验随谈

hongfei

个人成长 编程好习惯 经验总结

Redis集群架构剖析(2):槽位

非晓为骁

redis集群 slots 分布式,

CSDN终于破2万粉了,几百块钱的课程可白嫖,就是宠粉,Python笔试面试题

程序媛可鸥

Python 程序员 面试

36,Python基础开发与实践

程序媛可鸥

Python 程序员 面试

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