首頁 >web前端 >js教程 >掌握原型設計模式:綜合指南

掌握原型設計模式:綜合指南

Barbara Streisand
Barbara Streisand原創
2024-11-17 21:41:021053瀏覽

您是否曾經從庫中導入一個物件並嘗試克隆它,但卻失敗了,因為克隆它需要對庫的內部結構有廣泛的了解?

或者,在一個專案工作了很長時間之後,您休息一下重構程式碼,並注意到您正在程式碼庫的各個部分重新克隆許多複雜的物件?

嗯,原型設計模式已經滿足你了!

在本文中,我們將探索原型設計模式,同時建立功能齊全的日記範本 Node.js CLI 應用程式。

話不多說,讓我們開始吧!

概述

原型是一種創意設計模式,它是一類設計模式,用於處理使用new 創建物件的本機方式所帶來的不同問題。 關鍵字或運算子。

問題

工廠設計模式解決了以下創建問題:

  1. 如何複製應用程式中的現有物件而不依賴其具體類別?

  2. 一些複雜的物件很難克隆,因為它們要么有很多需要您不知道的特定業務邏輯的字段,要么有很多無法從外部訪問的私有字段物體。

讓我們以從 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 方法。

或建立一個通用介面

原型,它可以由所有可複製物件實現。 Mastering the Prototype Design Pattern: A Comprehensive Guide

擁有公共介面的一個好處是能夠在公共註冊服務類別中註冊所有原型,該服務類別將負責快取常用的原型並將其傳回給使用者。不必每次呼叫 clone 方法時都會複製物件。

這非常方便,尤其是在複製複雜物件時。

Mastering the Prototype Design Pattern: A Comprehensive Guide

實際場景

在本節中,我們將透過建立一個迷你日記範本 Nodejs CLI 應用程式來示範此設計模式。

正如我們之前所看到的,原型設計模式將把物件複製到物件本身的責任委託給物件。

但是你有沒有想過為什麼它被稱為原型?我的意思是這跟克隆有什麼關係?

我們將透過這個實際例子來回答這個問題,請繼續閱讀並關注。
您可以在此存儲庫中找到最終程式碼。只需克隆它並運行以下命令即可。

創建我們的原型:日記模板類

首先讓我們建立一個JournalTemplate,它有以下屬性:

  1. 名稱:我們需要它來辨識模板。
  2. 部分:部分是日記模板的一部分,保留用於特定主題或主題,例如:感恩、挑戰、明天的目標......

每個部分都包含以下屬性:

  • 標題該部分的主題或主題:感恩、挑戰、明天的目標...
  • 提示 當使用者要撰寫部分日誌文字時將向使用者顯示的訊息。

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. 提示使用者輸入範本的名稱,然後再次遞歸提示以建立任意數量的部分。
  2. 查看所有現有或已建立的範本。
  3. 使用範本建立日記文件條目。
  4. 從現有模板建立新模板:使用者將被要求選擇一個現有模板,然後他將能夠直接使用它或覆蓋其名稱和部分。

新建的範本可用於建立新的日記條目 (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 函數的工作原理如下:

  1. 我們首先從 registry 取得所有模板,然後循環返回的模板數組並使用我們之前在 JournalTemplate 中定義的 display 方法 上課。

使用範本建立日記條目:建立日記範本的原因,是為了讓我們在撰寫不同類型的日記時變得更輕鬆,而不是面對空白頁,更好的是更容易當面對一堆連續的章節標題和提示時,填寫日記。

讓我們深入了解 useTemplate 函數:

  1. 首先,我們從登錄取得範本名稱後,在現有範本中選擇一個範本。
  2. 對於範本中的每個部分,使用者都會要求開啟他喜歡的編輯器來填寫日記部分文字。

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()

從現有範本建立範本 :

最後,我們將看到原型設計模式的實際應用。

讓我們探索如何透過覆寫現有模板來動態建立新類型的模板

  1. 首先,我們提示使用者從現有範本中選擇他想要覆蓋的範本。
  2. 然後我們再次提示它輸入新建立的範本的名稱。
  3. 我們使用登錄來取得給定使用者選擇的範本名稱的範本。
  4. 我們使用clone方法來取得與所選模板相符的複製物件。

從下面的程式碼中可以看到,我們甚至不需要了解 JournalTemplate 類別的詳細信息,也不需要透過導入它來污染我們的程式碼。

TemplateActions.ts >從現有範本建立

  1. 最後,我們將使用者指定的範本名稱設為新建立的對象,然後提示使用者使用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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn