当前位置: 首页 > 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…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

【机器视觉】单目测距——运动结构恢复

ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛&#xf…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...

Unity UGUI Button事件流程

场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...

数据分析六部曲?

引言 上一章我们说到了数据分析六部曲&#xff0c;何谓六部曲呢&#xff1f; 其实啊&#xff0c;数据分析没那么难&#xff0c;只要掌握了下面这六个步骤&#xff0c;也就是数据分析六部曲&#xff0c;就算你是个啥都不懂的小白&#xff0c;也能慢慢上手做数据分析啦。 第一…...

网页端 js 读取发票里的二维码信息(图片和PDF格式)

起因 为了实现在报销流程中&#xff0c;发票不能重用的限制&#xff0c;发票上传后&#xff0c;希望能读出发票号&#xff0c;并记录发票号已用&#xff0c;下次不再可用于报销。 基于上面的需求&#xff0c;研究了OCR 的方式和读PDF的方式&#xff0c;实际是可行的&#xff…...

FTXUI::Dom 模块

DOM 模块定义了分层的 FTXUI::Element 树&#xff0c;可用于构建复杂的终端界面&#xff0c;支持响应终端尺寸变化。 namespace ftxui {...// 定义文档 定义布局盒子 Element document vbox({// 设置文本 设置加粗 设置文本颜色text("The window") | bold | color(…...