您是否曾经从库中导入一个对象并尝试克隆它,但却失败了,因为克隆它需要对库的内部结构有广泛的了解?
或者,在一个项目工作了很长时间之后,您休息一下重构代码,并注意到您正在代码库的各个部分重新克隆许多复杂的对象?
嗯,原型设计模式已经满足你了!
在本文中,我们将探索原型设计模式,同时构建功能齐全的日记模板 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 函数的工作原理如下:
使用模板创建日记条目:创建日记模板的原因,是为了让我们在撰写不同类型的日记时变得更轻松,而不是面对空白页,更好的是更容易当面对一堆连续的章节标题和提示时,填写日记。
让我们深入了解 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()
从现有模板创建模板 :
最后,我们将看到原型设计模式的实际应用。
让我们探索如何通过覆盖现有模板来动态创建新类型的模板。
从下面的代码中可以看到,我们甚至不需要了解 JournalTemplate 类的详细信息,也不需要通过导入它来污染我们的代码。
TemplateActions.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 } }
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中文网其他相关文章!