7个开发者必须知道的Nodejs 设计模式

好的,我已经收到了完整的内容,我将开始翻译。

设计模式是解决日常软件设计问题所必需的。这些问题可能包括:

  1. 维护数据库连接
  2. 创建和管理对象
  3. 通知一组订阅了特定实体的用户。

如果你试图自己想出解决方案,你很可能需要花费大量精力来解决这些问题,并设计这些问题的最优解决方案。

但其实你根本不需要这样做!

设计模式被引入是为了提供这些反复出现问题的解决方案。你所需要做的就是按照你的框架/语言实现模式,然后就可以运行了!

所以,让我们来看看你在使用 Node.js 时可能需要实现的最常见的设计模式。

1. 外观模式

首先,你需要了解外观模式。在 Node.js 应用程序中,这非常重要。

简单地说,外观设计模式通过提供统一的接口简化了复杂的子系统。

它充当一个单一的入口点,隐藏了所有内部实现细节,并促进了与底层功能的更轻松交互。它充当了一个门户,将客户端与复杂细节隔离开来。

例如,使用 Google 帐户登录网站的过程可以被认为是外观的一个现实世界例子。你只需要点击“使用 Google 登录”按钮,它就像是一个统一的登录选项。

你不需要担心输入你的电子邮件、密码或其他个人信息。

好处:

  • 简化接口:减少了开发人员与复杂系统的交互的认知负荷。
  • 减少耦合:将客户端代码与内部实现细节解耦,可以提高代码的可维护性和灵活性。
  • 提高可读性:将复杂逻辑封装在外观中,使代码更有组织性和可理解性。
  • 控制访问:在访问底层功能之前启用特定规则或验证。

考虑以下代码片段:


// 复杂模块  
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 的函数中,该函数处理所有内部复杂子系统通信。

所以你得到了一种清晰的方法来处理复杂的通信树。

2. 单例模式

接下来,这是你可能会在一生中每天都使用的模式之一。有时,你需要确保某物只有一个实例。

例如,考虑一个数据库连接。在给定时间内,你是否需要多个数据库连接到你的应用程序?你能重用现有连接吗?

这就是单例模式的作用。它确保你的类有一个全局实例,可以使用静态方法访问。

好处:

  • 全局访问:从应用程序的任何地方方便地访问共享数据或功能的一种方式。
  • 资源管理:通过仅有一个实例来有效使用资源,如数据库连接、日志记录器或文件句柄。
  • 一致性:只影响单个实例,从而强制实现一致的行为。
  • 控制状态:通过集中的数据操作点简化状态管理。

下面是在 Node.js 中实现单例的方式:


class ConfigManager {  constructor() {  this.databaseConfig = { /* 数据库配置 */ };  this.apiKey = "你的_api_密钥";  // 其他应用程序范围的配置  }  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 类,负责以集中的方式处理这些配置。

3. 适配器模式

接下来,你需要想象一个场景,你正在使用的 API 和你正在开发的客户端具有不兼容的 API。

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

  1. 名字

  2. 姓氏

但是你的 API 返回一个变量:

  1. 全名

所以,如果你处于无法更新 API Body 的位置,你将不得不使用你拥有的东西,并让你的应用程序工作。

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

适配器模式弥合了不兼容接口之间的差距,使它们能够无缝地协同工作。

好处:

  • 互操作性:使具有不同接口的组件之间能够通信,促进系统集成和重用。
  • 松散耦合:将客户端代码与适配组件的具体实现解耦,提高了灵活性和可维护性。
  • 灵活性:允许在不修改现有代码的情况下适配新组件,只需创建新的适配器。
  • 可重用性:适配器实现可以为类似的兼容性需求重复使用,减少代码重复。

示例:

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

旧系统

class OldSystem {  request() {  return "旧系统请求";  }  
}

新系统 & 适配器


class NewSystem {  newRequest() {  return "新系统请求";  }  
}  class Adapter {  constructor(newSystem) {  this.newSystem = newSystem;  }  request() {  return this.newSystem.newRequest();  }  
}

客户端用法


// 使用旧系统  
const oldSystem = new OldSystem();  
console.log(oldSystem.request()); // 输出:旧系统请求  // 使用带有新系统的适配器  
const newSystem = new NewSystem();  
const adapter = new Adapter(newSystem);  
console.log(adapter.request()); // 输出:新系统请求

4. 建造者模式

接下来,我们将看一种模式,你可以用它来构建对象,并使对象管理变得更容易。

建造者模式将复杂对象的构建与其表示分离。

这就像组装一台定制的 PC —— 逐个选择组件并构建最终产品。在 Node.js 中,建造者模式有助于以逐步、可定制的方式构建具有复杂配置的对象。

在这种设计模式中,你不是使用具有多个参数的构造函数,而是为对象的每个可选属性创建单独的方法(“构建器”)。这些方法通常返回类的当前实例 (this),以允许将它们链接在一起逐步构建对象。

好处:

  • 提高可读性:通过使用有意义的方法名称显式设置每个属性,代码更清晰。
  • 灵活性:只使用必要的属性构建对象,避免在未使用的字段中出现意外值。
  • 不可变性build() 方法通常创建一个新实例,而不是修改构建器,促进了不可变性和更容易的推理。
  • 错误处理:在建造者方法中更容易验证属性值并抛出错误,而不是在复杂的构造函数中。

示例:

下面是建造者设计模式的一个简单编码示例。


class UserBuilder {  constructor(name) {  this.name = name;  this.email = null;  this.address = null;  }  withEmail(email) {  this.email = email;  return this; // 方法链  }  withAddress(address) {  this.address = address;  return this;  }  build() {  // 验证并构建 User 对象  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); // 打印具有值的完整 User 对象

5. 工厂模式

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

可以将其想象成一个制造工厂,有不同的生产线用于生产不同的产品。在 Node.js 中,工厂模式在创建对象时不指定其具体类别方面表现出色,促进了灵活性和可扩展性。

优点:

  • 解耦:客户端代码与特定对象创建逻辑解耦,提高了灵活性和可维护性。
  • 集中控制:只要工厂处理了更改,就可以轻松添加新的对象类型或修改现有对象类型,而不会影响客户端代码。
  • 灵活性:根据运行时条件或配置选择适当的对象,使你的代码更具适应性。
  • 封装性:对象创建细节被隐藏在工厂内部,提高了代码的可读性和可维护性。

示例:

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

形状接口

// 形状接口  
class Shape {  draw() {}  
}

具体形状


// Shape 接口的具体实现  
class Circle extends Shape {  draw() {  console.log("绘制圆形");  }  
}  class Square extends Shape {  draw() {  console.log("绘制正方形");  }  
}  class Triangle extends Shape {  draw() {  console.log("绘制三角形");  }  
}

形状工厂


// ShapeFactory 类负责创建形状的实例  
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('无效的形状类型');  }  }  
}

客户端代码


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

6. 原型模式

原型模式涉及通过复制现有对象(称为原型)来创建新对象。

它创建了主键的副本。当创建一个对象比复制一个现有对象更昂贵时,这是很有用的。

概念:

  1. 原型:定义具有所需属性和方法的基础对象。这充当了后续对象的蓝图。
  2. 克隆:通过复制原型来创建新对象,通常使用像 Object.create 这样的内置方法或自定义克隆逻辑。
  3. 定制:新创建的对象可以修改其个别属性,而不会影响原型。

优点:

  • 性能:复制现有对象通常比从头开始构造新对象更快,特别是对于复杂对象而言。
  • 内存效率:通过原型共享属性和方法,避免了冗余存储,减少了内存使用。
  • 动态修改:可以轻松地稍后扩展原型以向所有现有和未来实例添加新功能。

示例:

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

原型对象


// 原型对象  
const animalPrototype = {  type: '未知',  makeSound: function () {  console.log('一些通用的声音');  },  clone: function () {  return Object.create(this); // 使用 Object.create() 进行克隆  },  
};

自定义实例


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

客户端代码


// 客户端代码使用自定义实例  
dog.makeSound(); // 输出: 汪汪!  
cat.makeSound(); // 输出: 喵喵!

7. 代理模式

代理模式充当另一个对象的替代或占位符,控制对它的访问。

这创建了一个中介对象(“代理”),它位于客户端和真实对象之间。这个代理控制对真实对象的访问,可能在达到目标之前或之后拦截和修改操作。

这允许你在不直接改变真实对象的实现的情况下添加额外的功能。代理模式对于延迟加载、访问控制或添加日志/调试功能至关重要。

优点:

  • 受控访问:在与真实对象交互之前强制执行权限或验证。
  • 额外功能:添加日志、缓存或安全等功能,而不改变对象本身。
  • 抽象:通过隐藏真实对象的实现细节,简化客户端代码。
  • 灵活性:在运行时动态更改目标对象或处理程序行为。

示例:

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

在所有这些示例中,我使用了 JavaScript 的 Proxy

象来为其他对象创建代理。要更深入地了解 JavaScript 内置的代理,你可以访问此处


const target = {  name: '艾丽斯',  sayHello() {  console.log(`你好,我叫 ${this.name}`);  },  
};  const handler = {  get(target, prop, receiver) {  console.log(`访问属性 ${prop}`);  return Reflect.get(target, prop, receiver);  },  set(target, prop, value, receiver) {  console.log(`将属性 ${prop} 设置为 ${value}`);  return Reflect.set(target, prop, value, receiver);  },  
};  const proxy = new Proxy(target, handler);  proxy.name; // 输出: 访问属性 name  
proxy.sayHello(); // 输出: 访问属性 sayHello  //      你好,我叫 艾丽斯  
proxy.name = '鲍勃'; // 输出: 将属性 name 设置为 鲍勃

结语

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

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

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

相关文章

数据结构(五)什么是算法

数据结构(五)什么是算法 要点:算法是一种指令集,有限且不依赖于某一种程序语言 思考:算法为什么是一种思想 01 算法的定义 一个有限指令集接收一些输入(有些情况下不需要输入)产生输出&…

使用 CloudFlare 后如何才能不影响搜索引擎蜘蛛爬虫

今天,明月给大家再次详细讲解一下,明月在使用 CloudFlare 后如何才能不影响搜索引擎蜘蛛爬虫对站点的抓取,因为这是很多首次使用 CloudFlare 的站长们容易忽略和触犯的问题,并不是 CloudFlare 不友好,而是 CloudFlare 的防火墙(WAF)实在是太给力。其实在【CloudFlare 如…

java项目之共享汽车管理系统(springboot+mysql+vue)

风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的共享汽车管理系统。项目源码以及部署相关请联系风歌,文末附上联系信息 。 项目简介: 共享汽车管理系统的主要…

为什么推荐将 IoTDB 服务地址配置为 HostName 而非 IP?

设置主机名启动 IoTDB 可在不修改配置情况下,在不同环境运行 IoTDB 并实现多次部署。 01 前言 IoTDB 在配置启动时有两种方式: 1. 通过设置 HostName(主机名)的方式来启动 IoTDB(推荐方式); 2. …

lua 判断字符串是否包含子字符串(点符号查找)

一、string.find 方法 lua代码 function containsDot(str) local pos string.find(str, ".") if pos then return true else return false end end -- 测试函数 local testString1 "hello.world" local testString2 "helloworld&quo…

SpringBoot集成jasypt对yml文件指定参数加密并自定义@bean隐藏密钥

1、查看SpringBoot和jasypt对应版本。 Jasypt 1.9.x 通常与 Spring Boot 1.5.x 相对应。 Jasypt 2.1.x 通常与 Spring Boot 2.0.x 相对应。 Jasypt 3.x 通常与 Spring Boot 2.1.x相对应。 2、引入maven <dependency><groupId>com.github.ulisesbocchio</groupI…

CSS - 选择器

目录 一、CSS的基本语法格式&#xff1a; 二、常见的CSS选择器 ​编辑1.标签选择器 2.类选择器 3.id选择器 4.复合选择器 5.通用选择器 三、常见的CSS样式 1.color 2.font-size 3.border 4.width/height 5.padding 6.margin 四、CSS的引入方式 1.行内引入 …

Tableau-BI仪表盘搭建

目录 经营数据总览 经营数据详情 每日营收数据 每日流量数据 新老客占比 平台占比 门店占比 投放情况 订单分布 配送分布 汇总搭建仪表板 构思仪表盘布局 经营数据总览 数据总览表&#xff0c;显示的是数据&#xff0c;就拖入文本中&#xff0c;其他同样加入到已经…

开源免费的定时任务管理系统:Gocron

Gocron&#xff1a;精准调度未来&#xff0c;你的全能定时任务管理工具&#xff01;- 精选真开源&#xff0c;释放新价值。 概览 Gocron是github上一个开源免费的定时任务管理系统。它使用Go语言开发&#xff0c;是一个轻量级定时任务集中调度和管理系统&#xff0c;用于替代L…

JavaEE初阶-多线程5

文章目录 一、线程池1.1 线程池相关概念1.2 线程池标准类1.3 线程池工厂类1.4 实现自己的线程池 二、定时器2.1 java标准库中的定时器使用2.2 实现一个自己的定时器2.2.1 定义任务类2.2.2 定义定时器 一、线程池 1.1 线程池相关概念 池这个概念在计算机中比较常见&#xff0c…

[笔试训练](十九)

目录 055:小易的升级之路 056:礼物的最大价值 057:对称之美 055:小易的升级之路 小易的升级之路_牛客题霸_牛客网 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 根据题意简单模拟即可&#xff0c;可单独写gcd函数求最大公因数。 int gcd(int a, int b) { if (…

抖音抖加如何投放效果最好?

抖音平台上的抖加投放是一种有效的推广方式,但对于一些新手来说,可能会遇到投放效果不佳的情况。为了帮助大家提高抖加投放的效,下面分享一些抖音抖加投放技巧。 一、选择合适的投放模式 抖音抖加有三种投放模式:系统智能投放、自定义定向投放以及达人相似粉丝投放。不同…

Redis——Redis数据分片的三种算法

Redis的数据分片通常是为了实现水平扩展&#xff0c;将数据分散到多个Redis节点上&#xff0c;以提高系统的容量和性能。在Redis的不同实现和集群方案中&#xff0c;数据分片的算法有所不同。以下是Redis数据分片的三种常见算法&#xff1a; 哈希取模分片&#xff08;Hash Modu…

数字水印 | 基于小波变换的数字水印技术

&#x1f34d;原文&#xff1a; 基于小波变换的数字水印技术 &#x1f34d;写在前面&#xff1a; 本文属搬运博客&#xff0c;自己留存学习。 正文 小波变换 把一个信号分解成由基本小波经过移位和缩放后的一系列小波&#xff0c;它是一种 “时间——尺度” 信号的多分辨率分…

Python中如何将小数转化为百分数进行输出

小数转化为百分数 Python中如何将小数转化为百分数进行输出基本概念使用字符串格式化1. 使用字符串格式化操作符 %2. 使用str.format()方法3. 使用f-string&#xff08;格式化字符串字面量&#xff09; **重点内容**&#xff1a;**无论是通过使用%格式化操作符、str.format()方…

AtCoder Beginner Contest 310 E题 NAND repeatedly

E题&#xff1a;NAND repeatedly 标签&#xff1a;动态规划题意&#xff1a;给定一个长度为 n n n的 01 01 01字符串 A i A_i Ai​&#xff0c;给定规则&#xff1a; 0 ⊼ 0 1 , 0 ⊼ 1 1 , 1 ⊼ 0 1 , 1 ⊼ 1 0 0⊼01,0⊼11,1⊼01,1⊼10 0⊼01,0⊼11,1⊼01,1⊼10。 求 ∑…

Linux进程间通信——匿名管道和命名管道

文章目录 一、引言二、管道的基本原理1、管道的定义与结构2、管道的工作原理 三、匿名管道&#xff08;Anonymous Pipe&#xff09;1、匿名管道的概念2、匿名管道的创建与使用3、匿名管道的读写规则4、匿名管道的特点5、使用匿名管道实现进程池 四、命名管道&#xff08;Named …

MySQL的复合查询

多表查询 MySQL中的多表查询是一个强大的功能&#xff0c;允许你在一个查询中组合来自多个表的数据。这通常通过JOIN语句实现&#xff0c;但也可以使用子查询或集合操作符&#xff08;如UNION&#xff09;。下面我将介绍几种常见的多表查询方式及其用途。 INNER JOIN&#xf…

学习java第六十八天

在AOP术语中&#xff0c;切面的工作被称为通知。通知实际上是程序运行时要通过Spring AOP框架来触发的代码段。 Spring切面可以应用5种类型的通知&#xff1a; 前置通知&#xff08;Before&#xff09;&#xff1a;在目标方法被调用之前调用通知功能&#xff1b; 后置通知&a…

计算机视觉——基于改进UNet图像增强算法实现

1. 引言 在低光照条件下进行成像非常具有挑战性&#xff0c;因为光子计数低且存在噪声。高ISO可以用来增加亮度&#xff0c;但它也会放大噪声。后处理&#xff0c;如缩放或直方图拉伸可以应用&#xff0c;但这并不能解决由于光子计数低导致的低信噪比&#xff08;SNR&#xff…