掌握原型设计模式:综合指南
您是否曾经从库中导入一个对象并尝试克隆它,但却失败了,因为克隆它需要对库的内部结构有广泛的了解?
或者,在一个项目工作了很长时间之后,您休息一下重构代码,并注意到您正在代码库的各个部分重新克隆许多复杂的对象?
嗯,原型设计模式已经满足你了!
在本文中,我们将探索原型设计模式,同时构建功能齐全的日记模板 Node.js CLI 应用程序。
话不多说,让我们开始吧!
概述
原型是一种创意设计模式,它是一类设计模式,用于处理使用new 创建对象的本机方式所带来的不同问题。 关键字或运算符。
问题
工厂设计模式解决了以下创建问题:
如何复制应用程序中的现有对象而不依赖于其具体类?
一些复杂的对象很难克隆,因为它们要么有很多需要您不知道的特定业务逻辑的字段,要么有很多无法从外部访问的私有字段物体。
让我们以从 socket.io 库 导入的 socket 对象为例,想象一下必须自己克隆它吗?
您将必须浏览库内的代码,了解套接字如何工作,这些对象甚至有一些循环依赖关系,您必须处理自己才能克隆它。
除此之外,您的代码将依赖于套接字类或接口以及相应的业务逻辑来创建它,这违反了可靠的依赖倒置原则,并使您的代码对于更改的鲁棒性较差。
解决方案
原型设计模式解决了这些问题,通过委托将对象复制到对象本身的责任,通过在每个对象的类中声明一个clone方法,该方法应该是可克隆。
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()
要实现原型设计模式,您可以直接在可克隆对象中包含
clone 方法。
或者创建一个通用接口原型,它可以由所有可克隆对象实现。
拥有公共接口的一个好处是能够在公共注册服务类中注册所有原型,该服务类将负责缓存常用的原型并将其返回给用户。不必每次调用 clone 方法时都克隆对象。
这非常方便,尤其是在克隆复杂对象时。
实际场景
在本节中,我们将通过构建一个迷你日记模板 Nodejs CLI 应用程序来演示此设计模式。
正如我们之前看到的,原型设计模式将把对象克隆到对象本身的责任委托给对象。
但是你有没有想过为什么它被称为原型?我的意思是这与克隆有什么关系?
我们将通过这个实际例子来回答这个问题,请继续阅读并关注。
您可以在此存储库中找到最终代码。只需克隆它并运行以下命令即可。
创建我们的原型:日记模板类
首先让我们创建一个JournalTemplate,它具有以下属性:
- 名称:我们需要它来识别模板。
- 部分:部分是日记模板的一部分,保留用于特定主题或主题,例如:感恩、挑战、明天的目标......
每个部分都包含以下属性:
- 标题该部分的主题或主题:感恩、挑战、明天的目标...
- 提示 当用户要编写部分日志文本时将向用户显示的消息。
JournalTemplate.ts
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()
JournalTemplate 类有许多实用方法来设置其不同的属性。
display 方法稍后将用于向终端显示彩色格式良好的输出。
chalk 包用于为输出的终端文本的一些片段着色。
我们的 JournalTemplate 对象顾名思义,用作创建其他模板或日记文件条目的模板或原型。
这就是为什么我们将 clone 方法添加到 JournalTemplate 类中。
我们添加它是为了将处理克隆业务逻辑的责任交给 JournalTemplate 对象本身,而不是消费代码。
声明日志模板注册
现在让我们创建 TemplateRegistry 类,它将负责存储 JournalTemplate 类原型实例。同时提供操作这些实例的方法。
TemplateRegistry.ts
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()
注册表将这些类存储在 Map 对象中,以便按名称快速检索,并公开许多用于添加或停用模板实例的实用方法。
实例化日志模板注册表
现在,让我们实例化模板寄存器,然后播种一些初始模板。
registry.ts
import chalk from "chalk" import { TemplateSection } from "./types" export interface TemplateSection { title: string prompt: string } export class JournalTemplate { constructor( public name: string, public sections: TemplateSection[] ) {} clone(): JournalTemplate { return new JournalTemplate( this.name, this.sections.map((s) => ({ ...s })) ) } display(): void { console.log(chalk.cyan(`\nTemplate: ${this.name}`)) this.sections.forEach((section, index) => { console.log(chalk.yellow(`${index + 1}. ${section.title}`)) console.log(chalk.gray(` Prompt: ${section.prompt}`)) }) } addSection(section: TemplateSection): void { this.sections.push(section) } removeSection(index: number): void { if (index >= 0 && index < this.sections.length) { this.sections.splice(index, 1) } else { throw new Error("Invalid section index") } } editSection(index: number, newSection: TemplateSection): void { if (index >= 0 && index < this.sections.length) { this.sections[index] = newSection } else { throw new Error("Invalid section index") } } getSectionCount(): number { return this.sections.length } getSection(index: number): TemplateSection | undefined { return this.sections[index] } setName(newName: string): void { this.name = newName } }
定义模板操作方法
在本节中,我们将定义一系列将在我们的应用程序菜单中使用的函数,以执行各种操作,例如:
- 提示用户输入模板的名称,然后再次递归提示以创建任意数量的部分。
- 查看所有现有或已创建的模板。
- 使用模板创建日记文件条目。
- 从现有模板创建新模板:用户将被要求选择一个现有模板,然后他将能够直接使用它或覆盖其名称和部分。
新创建的模板可用于创建新的日记条目 (1)。
创建模板 :
TemplateActions.ts >创建模板
import { JournalTemplate } from "./JournalTemplate" export class TemplateRegistry { private templates: Map<string, JournalTemplate> = new Map() addTemplate(name: string, template: JournalTemplate): void { this.templates.set(name, template) } getTemplate(name: string): JournalTemplate | undefined { const template = this.templates.get(name) return template ? template.clone() : undefined } getTemplateNames(): string[] { return Array.from(this.templates.keys()) } }
- 要创建模板,我们首先提示用户输入模板名称。
- 然后我们实例化一个新的模板对象,其中包含名称和各个部分的空数组。
- 之后,我们会提示用户输入各个部分的详细信息,输入每个部分的信息后,用户可以选择停止或进入更多部分。
utils.ts>提示部分详细信息
import { JournalTemplate } from "./JournalTemplate" import { TemplateRegistry } from "./TemplateRegistry" export const registry = new TemplateRegistry() registry.addTemplate( "Daily Reflection", new JournalTemplate("Daily Reflection", [ { title: "Gratitude", prompt: "List three things you're grateful for today.", }, { title: "Accomplishments", prompt: "What did you accomplish today?" }, { title: "Challenges", prompt: "What challenges did you face and how did you overcome them?", }, { title: "Tomorrow's Goals", prompt: "What are your top 3 priorities for tomorrow?", }, ]) ) registry.addTemplate( "Weekly Review", new JournalTemplate("Weekly Review", [ { title: "Highlights", prompt: "What were the highlights of your week?" }, { title: "Lessons Learned", prompt: "What important lessons did you learn this week?", }, { title: "Progress on Goals", prompt: "How did you progress towards your goals this week?", }, { title: "Next Week's Focus", prompt: "What's your main focus for next week?", }, ]) )
promptForSectionDetails 函数使用 inquirer 包询问标题,然后按顺序向用户提示。
查看模板 :
TemplateActions.ts >视图模板
import chalk from "chalk" import inquirer from "inquirer" import { JournalTemplate } from "./JournalTemplate" import { registry } from "./registry" import { editTemplateSections } from "./templateSectionsActions" import { promptForSectionDetails } from "./utils" export async function createTemplate(): Promise<void> { const { name } = await inquirer.prompt<{ name: string }>([ { type: "input", name: "name", message: "Enter a name for the new template:", }, ]) const newTemplate = new JournalTemplate(name, []) let addMore = true while (addMore) { const newSection = await promptForSectionDetails() newTemplate.addSection(newSection) const { more } = await inquirer.prompt<{ more: boolean }>([ { type: "confirm", name: "more", message: "Add another section?", default: false, }, ]) addMore = more } registry.addTemplate(name, newTemplate) console.log(chalk.green(`Template "${name}" created successfully!`)) }
viewTemplates 函数的工作原理如下:
- 我们首先从 registry 获取所有模板,然后循环返回的模板数组并使用我们之前在 JournalTemplate 中定义的 display 方法 上课。
使用模板创建日记条目:创建日记模板的原因,是为了让我们在撰写不同类型的日记时变得更轻松,而不是面对空白页,更好的是更容易当面对一堆连续的章节标题和提示时,填写日记。
让我们深入了解 useTemplate 函数:
- 首先,我们从注册表获取模板名称后,在现有模板中选择一个模板。
- 对于模板中的每个部分,用户都会要求打开他喜欢的编辑器来填写日记部分文本。
TemplateActions.ts >使用模板
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()
从现有模板创建模板 :
最后,我们将看到原型设计模式的实际应用。
让我们探索如何通过覆盖现有模板来动态创建新类型的模板。
- 首先,我们提示用户从现有模板中选择他想要覆盖的模板。
- 然后我们再次提示它输入新创建的模板的名称。
- 我们使用注册表来获取给定用户选择的模板名称的模板。
- 我们使用clone方法来获取与所选模板匹配的克隆对象。
从下面的代码中可以看到,我们甚至不需要了解 JournalTemplate 类的详细信息,也不需要通过导入它来污染我们的代码。
TemplateActions.ts >从现有模板创建
- 最后,我们将用户指定的模板名称设置为新创建的对象,然后提示用户使用 editTemplateSections 方法对现有模板部分执行任何增删改查操作,我们将使用该方法在代码块之后解释如下。
import chalk from "chalk" import { TemplateSection } from "./types" export interface TemplateSection { title: string prompt: string } export class JournalTemplate { constructor( public name: string, public sections: TemplateSection[] ) {} clone(): JournalTemplate { return new JournalTemplate( this.name, this.sections.map((s) => ({ ...s })) ) } display(): void { console.log(chalk.cyan(`\nTemplate: ${this.name}`)) this.sections.forEach((section, index) => { console.log(chalk.yellow(`${index + 1}. ${section.title}`)) console.log(chalk.gray(` Prompt: ${section.prompt}`)) }) } addSection(section: TemplateSection): void { this.sections.push(section) } removeSection(index: number): void { if (index >= 0 && index < this.sections.length) { this.sections.splice(index, 1) } else { throw new Error("Invalid section index") } } editSection(index: number, newSection: TemplateSection): void { if (index >= 0 && index < this.sections.length) { this.sections[index] = newSection } else { throw new Error("Invalid section index") } } getSectionCount(): number { return this.sections.length } getSection(index: number): TemplateSection | undefined { return this.sections[index] } setName(newName: string): void { this.name = newName } }
templateSectionsAction >编辑模板部分
import { JournalTemplate } from "./JournalTemplate" export class TemplateRegistry { private templates: Map<string, JournalTemplate> = new Map() addTemplate(name: string, template: JournalTemplate): void { this.templates.set(name, template) } getTemplate(name: string): JournalTemplate | undefined { const template = this.templates.get(name) return template ? template.clone() : undefined } getTemplateNames(): string[] { return Array.from(this.templates.keys()) } }
下面定义的 editTemplateSections 基本上会提示显示一个菜单,要求用户通过提供不同的操作来覆盖现有部分,例如:
- 添加部分
- 删除部分
- 编辑部分
应用菜单
最后,我们利用 index.ts 文件中的所有先前函数,该文件引导 cli 应用程序,并显示带有不同模板操作选项的菜单:
- 创建模板。
- 从现有模板创建模板。
- 查看模板。
- 使用模板创建日记条目。
- 退出程序。
index.ts
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()
结论
原型设计模式提供了一种通过克隆现有对象来创建新对象的强大方法。在我们的日记模板应用程序中,我们已经看到该模式如何允许我们基于现有模板创建新模板,展示了原型模式的灵活性和效率。
通过使用此模式,我们创建了一个易于扩展和修改的系统,展示了面向对象设计模式在实际应用程序中的真正威力。
接触
如果您有任何疑问或想进一步讨论,请随时与我联系。
编码愉快!
以上是掌握原型设计模式:综合指南的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

JavaScript是现代Web开发的核心语言,因其多样性和灵活性而广泛应用。1)前端开发:通过DOM操作和现代框架(如React、Vue.js、Angular)构建动态网页和单页面应用。2)服务器端开发:Node.js利用非阻塞I/O模型处理高并发和实时应用。3)移动和桌面应用开发:通过ReactNative和Electron实现跨平台开发,提高开发效率。

本文展示了与许可证确保的后端的前端集成,并使用Next.js构建功能性Edtech SaaS应用程序。 前端获取用户权限以控制UI的可见性并确保API要求遵守角色库

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

我使用您的日常技术工具构建了功能性的多租户SaaS应用程序(一个Edtech应用程序),您可以做同样的事情。 首先,什么是多租户SaaS应用程序? 多租户SaaS应用程序可让您从唱歌中为多个客户提供服务
