当前位置: 首页 > news >正文

锐评 Nodejs 设计模式 - 创建与结构型

本系列文章的思想,都融入了 让 Java 再次伟大 这个全新设计的脚手架产品中,欢迎大家使用。

单例模式与模块系统

Node 的单例模式既特殊又简单——凡是从模块中导出的实例天生就是单例。

// database.js
function Database(connect, account, password) {this.connect = connect;this.account = account;this.password = password;
}
const database = new Database("localhost", "root", "123456");
export default database;// index.js
import database1 from "./database.js";
import database2 from "./database.js";console.log(database1 === database2); // true

这是由于 Node.js 加载完某个模块后,会创建模块与模块标识符
(module specifier) 的映射关系并将其缓存起来供后续使用,就像下面这样:

pseudo code (CommonJS module)

function loadModule(filename, module, require) {const wrappedSrc = `(function(module,exports,require) {${fs.readFileSync(filename, "utf8")} })(module,module.exports,require)`;eval(wrappedSrc);
}function require(moduleName) {const id = require.resolve(moduleName);// 加载缓存if (require.cache[id]) {return require.cache[id].exports;}const module = { exports: {} };// 缓存模块require.cache[id] = module;loadModule(id, module, require);return module.exports;
}
require.cache = {};
require.resolve = function (moduleName) {return moduleName;
};

得益于 CommonJS module 的缓存功能,在 Node 中我们不再需要专门的单例设计模式了。

循环依赖

CommonJS module 基于一个简单的原理,在立即执行函数(IIFE)中创建私有域,然后一边执行代码一边构建图。如果 a,b 两个模块互相依赖,在 a 模块执行完之前 b 模块中引用了 a 模块导出的
变量,就可能会导致 b 模块无法看到 a 模块的最终状态。
ECMAScript Modules 解决这个问题的方式是先通过分析构建图,当图构建完毕以后使用后序深度优先遍历从最后一个节点开始执行代码。这样能保证当其他节点依赖这个模块时,获取到的模块导出变量为最终状态,不会再被 b 模块自身改动。

实时绑定

CommonJS module 的 import 是通过将 b 模块导出对象进行浅拷贝到 a 模块的作用域中来实现的。如果对象的属性是原始类型的话,a 模块获得的是一份拷贝,b 模块的修改可能无法反映到这份拷贝上。
而 ECMAScript Modules 有一种名叫 live bindings 机制,使得 a 模块 import 的 b 模块是一个链接,它指向了 b 模块导出的那个对象。如果 b 模块在运行过程中更改了对象中的属性,a 模块马上就可以获取到最新的状态。而 CommonJS module 的 import 是通过将 b 模块导出对象进行浅拷贝到 a 模块的作用域中来实现的。如果对象的属性是原始类型的话,a 模块获得的是一份拷贝,b 模块的修改可能无法反映到这份拷贝上。

Revealing Constructor

如果你想设计一个只能在初始化时改变状态的类,就可以采用这种技巧。或者换一种说法:该类实例化完毕后就不可变(immutable)。

可能你会觉得 Revealing Constructor 有点陌生,因为它不是我们熟悉的那种设计模式。不过不用担心,因为你一定见过它了。在 Node.js 中这个模式运用的很广泛,其中最常见的就是 Promise。

pseudo code

class TinyPromise {constructor(executor) {this.status = "pending";this.value = undefined;executor(this.resolve);}resolve = (value) => {this.status = "resolve";this.value = value;};then = (func) => {if (this.status === "resolve") {func(this.value);}};
}new TinyPromise((resolve) => {resolve(1);
}).then((value) => {console.log(value);
});
// output: 1

Proxy 代理装饰与适配

ES 内置的 Proxy 支持在运行期 (runtime) 创建代理对象,使通过组合和委派解决问题的设计模式都能通过 Proxy 的方式得到体现,消除了硬编码代理类产生的冗余代码。

function Subject() {this.morning = () => {console.log("morning");};this.bye = () => {console.log("bye");};
}const subject = new Subject();let subjectHandler = {get: function (target, prop) {// 返回增强后的方法if (prop === "morning") {return function () {// 增强逻辑console.log("good");// 调用原对象逻辑target[prop]();};}// 返回原对象中的属性return target[prop];},// set、has、delete...
};const enhanceSubject = new Proxy(subject, subjectHandler);
enhanceSubject.morning(); // good morning
enhanceSubject.bye(); // bye

通过 Proxy 表达的代理、装饰器、适配器设计模式看起来都差不多,差别主要在于应用场景:

  1. 代理强调对某个对象的访问的控制。
  2. 装饰器强调对对象属性的增强。
  3. 适配器则提供与原对象不同的行为以适配新的场景。

Proxy 与对象虚拟化

下面这个例子实现了一个涵盖所有偶数的虚拟化数组——具备数组的行为但不实际存储数据。

const eventNumbers = new Proxy([], {get: (target, prop) => index * 2,has: (target, number) => number % 2 === 0,
});
console.log(2 in eventNumbers); // true
console.log(3 in eventNumbers); // false
console.log(eventNumbers[7]); // 14

举这个例子是想说明,Proxy 的作用不仅仅局限于设计模式,还有很多其他功能可以用 Proxy 来体现,比如:「元编程 (meta-programming)、运算符重载 (operator overloading) 和对象虚拟化 (object virtualization)」

Change Observer 与响应式编程

结合 Proxy 的 set 方法 (trapMethod) 还可以实现观察者模式——当某个对象的状态发生变化时通知观察者。

在强类型语言中观察者一般是一个对象,由于 js 中的所有函数都是闭包的缘故,所以观察者可以是一个回调函数。

// 被观测对象
const invoice = {subtotal: 100,discount: 10,tax: 20,
};// 状态发生变化时的回调函数(计算票据总额)
function calculateTotal(invoice) {return invoice.subtotal - invoice.discount + invoice.tax;
}
// target: 被代理对象 observe: 回调函数(calculateTotal)
function createObservable(target, observe) {const observable = new Proxy(target, {// obj: 原始对象 prop: 属性 value: 值set(obj, prop, value) {// 如果待设置的属性值和之前的属性不同if (value !== obj[prop]) {const prev = obj[prop];obj[prop] = value;// callbackobserve({ prop, prev, curr: value });}return true;},});return observable;
}let total = calculateTotal(invoice);
console.log(total); // 120// 创建代理对象(观测该对象属性发生变化)
const obsInvoice = createObservable(invoice, (change) => {console.log(change);console.log(calculateTotal(invoice));
});// 打印票据 total: 210
obsInvoice.subtotal = 200;
// 打印票据 total: 200
obsInvoice.discount = 20;
// 打印票据 total: 200
obsInvoice.discount = 20;
// 打印票据 total: 210
obsInvoice.tax = 30;

像上例这样由实例状态的变化来驱动业务,而不是由客户端(调用方)来发起业务调用的编程范式,称为响应式编程。(reactive programming RP)

结语

  1. 代理、装饰器、适配器都是结构型模式,体现的设计思想都差不多——通过对象的结构来管理实体间的关系。
  2. Proxy 不仅用来实现设计模式,也可以用来完成很多其他功能和编程范式。
  3. 就像 js 中的闭包函数一样,模块导出天生就是单例。

相关文章:

锐评 Nodejs 设计模式 - 创建与结构型

本系列文章的思想,都融入了 让 Java 再次伟大 这个全新设计的脚手架产品中,欢迎大家使用。 单例模式与模块系统 Node 的单例模式既特殊又简单——凡是从模块中导出的实例天生就是单例。 // database.js function Database(connect, account, password)…...

【RoadRunner】自动驾驶模拟3D场景构建 | 软件简介与视角控制

💯 欢迎光临清流君的博客小天地,这里是我分享技术与心得的温馨角落 💯 🔥 个人主页:【清流君】🔥 📚 系列专栏: 运动控制 | 决策规划 | 机器人数值优化 📚 🌟始终保持好奇心&…...

15分钟学Go 第4天:Go的基本语法

第4天:基本语法 在这一部分,将讨论Go语言的基本语法,了解其程序结构和基础语句。这将为我们后续的学习打下坚实的基础。 1. Go语言程序结构 Go语言程序的结构相对简单,主要包括: 包声明导入语句函数语句 1.1 包声…...

【Qt】Qt的介绍——Qt的概念、使用Qt Creator新建项目、运行Qt项目、纯代码方式、可视化操作、认识对象模型(对象树)

文章目录 Qt1. Qt的概念2. 使用Qt Creator新建项目3. 运行Qt项目3.1 纯代码方式实现3.2 可视化操作实现 4. 认识对象模型(对象树) Qt 1. Qt的概念 Qt 是一个跨平台的 C 图形用户界面应用程序开发框架。它是软件开发者提供的用于界面开发的程序框架&#…...

论文笔记:PTR: Prompt Tuning with Rules for Text Classification

Abstract 手动设计大量语言提示麻烦且易出错,而自动生成的提示,在非小样本场景下验证其有效性昂贵且耗时。因此,提示调优以处理多类别分类任务仍然具有挑战。为此,本文提出使用规则进行多类别文本分类提示调优(PTR&…...

服务器和中转机协同工作以提高网络安全

服务器和中转机(代理服务器)可以通过多种方式协同工作来提高网络安全。 常见的协同工作策略: 1. 使用代理服务器作为安全网关 访问控制:代理服务器可以作为网络的入口点,实施访问控制策略,如基于IP地址、…...

Java利用itextpdf实现pdf文件生成

前言 最近公司让写一个数据页面生成pdf的功能,找了一些市面代码感觉都太麻烦,就自己综合性整合了一个便捷的工具类,开发只需简单组装数据直接调用即可快速生成pdf文件。望大家一起学习!!! 代码获取方式&am…...

2010年国赛高教杯数学建模C题输油管的布置解题全过程文档及程序

2010年国赛高教杯数学建模 C题 输油管的布置 某油田计划在铁路线一侧建造两家炼油厂,同时在铁路线上增建一个车站,用来运送成品油。由于这种模式具有一定的普遍性,油田设计院希望建立管线建设费用最省的一般数学模型与方法。   1. 针对两炼…...

datawhale大模型bot应用开发--task3:工作流

目录 一、介绍:Coze工作流 1.1工作流应用场景 1.2什么是工作流 1.3思考环节 二、各个工作流详情 2.1情感分类工作流 2.2 随机数工作流 2.3 必应搜索工作流 2.4 天气查询工作流 三、集合上面五个工作流的总工作流 一、介绍:Coze工作流 1.1工作…...

期货配资系统风控逻辑开发/完整源代码

期货配资系统风控逻辑的开发是确保系统安全、稳定、高效运行的关键环节。以下是对期货配资系统风控逻辑开发的详细分析: 一、风险识别与评估 风险来源分析: 市场风险:期货市场价格波动带来的风险。信用风险:投资者或配资方违约的…...

汽车免拆诊断案例 | 2023款零跑C01纯电车后备厢盖无法电动打开和关闭

故障现象  一辆2023款零跑C01纯电车,累计行驶里程约为2万km,车主进厂反映,后备厢盖无法电动打开和关闭。 故障诊断  接车后试车,操作后备厢盖外侧、驾驶人侧及遥控钥匙上的后备厢盖开启按钮,可以听到后备厢盖解锁的…...

分布式存储架构 与分布式一致性协议

分布式存储架构可以分为无中心节点架构和有中心节点架构。它们的设计在系统中的角色分配、数据管理、协调方式等方面有所不同。 1. 无中心节点架构(Decentralized/Peer-to-Peer Architecture) 在无中心节点的分布式存储架构中,所有节点都是…...

Unity Apple Vision Pro 保姆级开发教程 - Simulator 模拟器使用

教程视频 Apple VisionPro Simulator 模拟器使用教程 VsionOS Simulator 简介 visionOS Simulator 是一个用于开发和测试 visionOS 应用程序的工具。它模拟 Apple Vision Pro 的运行环境,帮助开发者在没有硬件设备的情况下创建、调试和优化他们的应用程序。VisionO…...

Vue 之 插件与组件的区别

在 Vue.js 中,插件(Plugin)和组件(Component)都是用来扩展 Vue 功能的重要工具,但它们的应用场景和使用方式有所不同。本文将通过对比的方式,帮助开发者更好地理解两者的区别,并通过…...

了解 ChatGPT 中的公平性问题

了解 ChatGPT 中的公平性问题 最近,OpenAI 又发布了一篇新的博客。他们谈论了一个有趣又重要的话题——用户的身份如何影响 ChatGPT 的回答。 这项研究揭示了一个鲜明的事实,那就是 AI 可能会无意间对人类产生刻板印象。很可能这些刻板印象源自 AI 训练过程中使用的数据,而这…...

【PHP】安装swoole时报错:No package ‘libbrotlienc‘ found

一、环境 Debian 11(bullseye) PHP 8.2.14 Swoole 5.1.4 二、过程 今天在安装Swoole 5.1.4的时候报错,错误信息如下: configure: error: Package requirements (libbrotlienc) were not met:No package libbrotlienc foundConsider adjusting the PK…...

postgresql执行计划解读案例

简介 SQL优化中读懂执行计划尤其重要,以下举例说明在执行计划中常见的参数其所代表的含义。 创建测试数据 -- 创建测试表 drop table if exists customers ; drop table if exists orders ; drop table if exists order_items ; drop table if exists products ;…...

Matlab实现粒子群优化算法优化随机森林算法模型 (PSO-RF)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1内容介绍 粒子群优化算法(PSO)是一种启发式搜索方法,灵感来源于鸟类群体觅食的行为。在PSO中,每个解都是搜索空间中的一个“粒子”,这些粒子以一定的速度飞行&am…...

使用 EasyExcel 相邻数据相同时行和列的合并,包括动态表头、数据

前言 在处理 Excel 文件时,经常会遇到需要对表格中的某些单元格进行合并的情况,例如合并相同的行或列。Apache POI 是一个强大的工具,但它使用起来相对复杂。相比之下,EasyExcel 是一个基于 Apache POI 的轻量级 Excel 处理库&am…...

985研一学习日记 - 2024.10.16

一个人内耗,说明他活在过去;一个人焦虑,说明他活在未来。只有当一个人平静时,他才活在现在。 日常 1、起床6:00√ 2、健身1个多小时 今天练了二头和背部,明天练胸和三头 3、LeetCode刷了3题 旋转图像&#xff1a…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​,覆盖应用全生命周期测试需求,主要提供五大核心能力: ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...