ホームページ > ウェブフロントエンド > jsチュートリアル > 抽象ファクトリー パターンをマスターする: 包括的なガイド

抽象ファクトリー パターンをマスターする: 包括的なガイド

Susan Sarandon
リリース: 2024-12-05 20:41:14
オリジナル
902 人が閲覧しました

アプリケーション内でロジックを何度も複製せずに、オブジェクトのさまざまなファミリーの複数のバリエーションを作成する必要があると感じたことはありますか?

あるいは、アプリケーションを構築した後で、新しい要件やクライアントの設定の変更により、まったく新しいオブジェクトが必要になり、コードベース全体の作り直しが必要になったことに気づいたということもあるでしょうか?

既存のコードを壊すことなく、新しい実装をプラグインするだけで、シームレスに新しいバリエーションを導入できる方法があったとしたらどうでしょうか?

そこで、Abstract Factory デザイン パターンが登場します!

このチュートリアルでは、複数の形式とテーマをサポートする複数の種類の履歴書を作成するための Node.js CLI アプリケーション を構築することで、この強力なデザイン パターンを詳しく説明します。

概要

Abstract Factory創造的なデザイン パターン であり、新しいキーワードまたは演算子。

Abstract Factory 設計パターンは、このブログ記事で説明したファクトリ メソッド設計パターンを一般化したものと考えることができます。

問題

Abstract Factory デザイン パターンは、次の問題を解決します。

  1. PDFResumeJSONResumeMarkdownResume などの 関連製品ファミリー を作成するにはどうすればよいですか?
  2. CreativeResumeMinimalistResumeModernResume など、製品ファミリーごとに複数のバリエーションをサポートするにはどうすればよいですか?
  3. 既存の消費コードやクライアント コードを壊さずに、バリアントや製品の追加をサポートするにはどうすればよいですか?
解決

Abstract Factory デザイン パターンは、製品の種類ごとにインターフェイスまたは抽象クラスを宣言することで、これらの問題を解決します。

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
そして、パターンの名前が示すように、

抽象ファクトリーを作成します。これは、あらゆるタイプの製品を作成するファクトリー メソッドを宣言するインターフェイスです。

  • createPDFResume : PDFResume タイプまたはサブタイプを返します。
  • createMarkdownResume : MarkdownResume タイプまたはサブタイプを返します。
  • createJSONResume : JSONResume タイプまたはサブタイプを返します。
export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
これで、考えられるすべてのタイプの製品を返す汎用ファクトリーができました。しかし、製品ごとに複数のバリエーションをサポートするにはどうすればよいでしょうか?

答えは、抽象ファクトリー ( ResumeFactory ) を実装する ConcreteFactory を作成することです。

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

クライアント クラスでファクトリを使用するには、ResumeFactory 型の変数を宣言し、ユーザー入力に応じて対応する コンクリート ファクトリ をインスタンス化するだけです。

クライアントコード:

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

構造

Mastering the Abstract Factory Pattern: A Comprehensive Guide

Abstract Factory デザイン パターンの構造は次のクラスで構成されます:

  1. Factory : この設計パターンに abstract Factory という名前を付けた理由は、このクラスがすべての ConcreteFactories 間のコントラクトを表すためです。すべてのファクトリ メソッドを定義します。
  • ファクトリ メソッドの数は製品の数と同じです。
  • ファクトリ メソッドは、抽象または汎用の製品タイプ ( IProduct{j} ) を返す必要があります。

この場合、Factory で宣言されたファクトリ メソッドは次のとおりです: createProductA と createProductB

  1. ConcreteFactory{i} : これらのクラスは Factory クラスを実装し、各 ファクトリ メソッド のカスタム実装を提供します。
  • 上記のスキーマでは、i は 1 または 2 に等しいです。
  • ConcreteFactories の数は、製品ごとに可能なバリアントの数と同じです。
  • 各具体的なファクトリ メソッドは、対応する製品のインスタンスであるオブジェクトを返す必要があります。
  1. IProduct{j} : これらのクラスは抽象製品タイプに対応します。
  • 上記のスキーマでは、j は A または B のいずれかに相当します。
  • IProduct{j} は、多くの具体的な製品クラスによって実装されます。

ConcretProductA1 と ConcretProductA2 は IProductA を実装します。 ConcretProductB1 と ConcretProductB2 は IProductB を実装します

  1. ConcreteProducts は、IProduct{j} ジェネリック型の 1 つを実装するプロダクトです。

実践的なシナリオ

このセクションでは、ユーザーが選択したテーマと形式に基づいて履歴書を作成する、完全に動作する Node.js TypeScript CLI アプリケーションを構築することで、前の例を実行します。

ご使用のマシン上でこのリポジトリのクローンを作成して、完全に動作するコードを自由にチェックアウトしてください。

次に、次のコマンドを実行します。

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

型の宣言

型安全性を確保するために、チュートリアル全体で使用する型を宣言することから始めましょう。

インターフェース/タイプ

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
  1. ResumeData タイプは、名前、電子メール、電話番号、経験の配列などの履歴書オブジェクトのすべての属性を定義します。
  2. 経験 タイプは、会社、役職、開始日、終了日、説明で構成されます。

抽象ファクトリーの宣言

次に、ジェネリック ファクトリ タイプを宣言しましょう。これは、サポートされているさまざまな製品タイプに対応する 3 つの ファクトリ メソッド を定義します: PDFResumeMarkdownResume 、およびJSONResume.

インターフェース/ResumeFactory

export class CreativeResumeFactory implements ResumeFactory {
  createPDFResume(): CreativePDFResume {
    return new CreativePDFResume() // CreativePDFResume implements PDFResume
  }

  createMarkdownResume(): CreativeMarkdownResume {
    return new CreativeMarkdownResume() // CreativeMarkdownResume implements MarkdownResume
  }

  createJSONResume(): CreativeJSONResume {
    return new CreativeJSONResume() // CreativeJSONResume implements JSONResume
  }
}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

次のセクションでコードについて説明します。

さまざまな種類のドキュメントの共有クラスの宣言

次に、汎用製品クラスの作成に進みましょう。

対応するサブタイプ間で属性とメソッドの両方を共有したいため、すべての製品タイプは抽象クラスになります。

  1. JSONResume : このクラスには保護された data 属性があり、style という追加属性を持つ ResumeData 型のオブジェクトを保存します。

クラスは以下を定義します:

  • data 属性にアクセスするためのゲッター メソッド。
  • 抽象 generate メソッド。後でサブクラスによってオーバーライドされます。
  • 基本的な実装を備えた saveToFile メソッド。これは、履歴書データを JSON ファイルに保存することで構成されます。

履歴書/json/JSONResume

// User inputs...
let theme = "minimalist"
let format = "pdf"

let factory: ResumeFactory

switch (theme) {
  case "minimalist":
    factory = new MinimalistResumeFactory()
    break
  case "modern":
    factory = new ModernResumeFactory()
    break
  case "creative":
    factory = new CreativeResumeFactory()
    break
  default:
    throw new Error("Invalid theme.")
}

const userInput = await getUserInput()
let resume

switch (format) {
  case "pdf":
    resume = factory.createPDFResume()
    break
  case "markdown":
    resume = factory.createMarkdownResume()
    break
  case "json":
    resume = factory.createJSONResume()
    break
  default:
    throw new Error("Invalid format.")
}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

キーワード abstract は、クラスがインスタンス化できないジェネリック型であることを意味します。他のクラスによってのみ継承できます。

  1. MarkdownResume : クラスには保護された content 属性があり、マークダウン文字列を保存します。

クラスは以下を定義します:

  • content 属性にアクセスするためのゲッター メソッド。
  • 抽象 generate メソッド。後でサブクラスによってオーバーライドされます。
  • saveToFile メソッドは、fileName を取得し、マークダウン形式の文字列 content をファイルに保存します。

再開/マークダウン/MarkdownResume

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
  1. PDF履歴書 :

クラスには、 PDFKit.PDFDocument 型の保護された doc オブジェクトがあり、これは pdfkit というライブラリからインポートされます。このライブラリは、オブジェクト指向インターフェイスを通じて PDF ドキュメントの作成と操作を簡素化します。

クラスは以下を定義します:

  • doc 属性にアクセスするためのゲッター メソッド。
  • 抽象 generate メソッド。後でサブクラスによってオーバーライドされます。
  • doc メモリ内の PDF オブジェクトを特定のファイルに保存する saveToFile メソッド。

履歴書/pdf/PDF履歴書

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

コンクリート工場の宣言

汎用製品タイプ抽象ファクトリー を定義したので、次に、ConcreteFactories の作成に進みます。すべてのジェネリック製品タイプの異なるバリエーション。

履歴書には、クリエイティブミニマリスト、および モダンの 3 つのバリエーションがあります。そして、3 種類の汎用プロダクト: JSONPDF 、および Markdown

抽象ファクトリ (ResumeFactory) は、製品の作成を担当する 3 つのファクトリ メソッドを定義します。

  • createPDFResume : PDFResume タイプのインスタンスを作成します。
  • createMarkdownResume : MarkdownResume タイプのインスタンスを作成します。
  • createJSONResume : JSONResume 型のインスタンスを作成します。

製品ごとに複数のバリエーションをサポートするには、3 つのコンクリート工場を作成する必要があります。

コンクリート工場は、独自の味を持つ 3 種類の製品を作成します。

  1. CreativeResumeFactory は、Creative バリアントの製品を作成します。
  2. MinimalistResumeFactory は、Minimalist バリアントの製品を作成します。
  3. ModernResumeFactory は、Modern バリアントの製品を作成します。

ファクトリー/CreativeResumeFactory

export class CreativeResumeFactory implements ResumeFactory {
  createPDFResume(): CreativePDFResume {
    return new CreativePDFResume() // CreativePDFResume implements PDFResume
  }

  createMarkdownResume(): CreativeMarkdownResume {
    return new CreativeMarkdownResume() // CreativeMarkdownResume implements MarkdownResume
  }

  createJSONResume(): CreativeJSONResume {
    return new CreativeJSONResume() // CreativeJSONResume implements JSONResume
  }
}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
  • CreativeResumeFactory ファクトリ メソッドは、あらゆる種類の製品のクリエイティブ具体的な製品バリアントを返します。

factories/MinimalistResumeFactory

// User inputs...
let theme = "minimalist"
let format = "pdf"

let factory: ResumeFactory

switch (theme) {
  case "minimalist":
    factory = new MinimalistResumeFactory()
    break
  case "modern":
    factory = new ModernResumeFactory()
    break
  case "creative":
    factory = new CreativeResumeFactory()
    break
  default:
    throw new Error("Invalid theme.")
}

const userInput = await getUserInput()
let resume

switch (format) {
  case "pdf":
    resume = factory.createPDFResume()
    break
  case "markdown":
    resume = factory.createMarkdownResume()
    break
  case "json":
    resume = factory.createJSONResume()
    break
  default:
    throw new Error("Invalid format.")
}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
  • MinimalistResumeFactory ファクトリ メソッドは、あらゆる種類の製品のミニマリストの具体的な製品バリアントを返します。

factory/ModernResumeFactory

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
  • ModernResumeFactory ファクトリ メソッドは、あらゆる種類の製品の最新の具体的な製品バリアントを返します。

クリエイティブな履歴書工場のコンクリート製品

次に、CreativeResumeFactory によって返される前の ConcreteProducts

を作成しましょう。

PDF 履歴書 :

履歴書/pdf/CreativePDFResume

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

マークダウンの再開 :

再開/マークダウン/CreativeMarkdownResume

export class CreativeResumeFactory implements ResumeFactory {
  createPDFResume(): CreativePDFResume {
    return new CreativePDFResume() // CreativePDFResume implements PDFResume
  }

  createMarkdownResume(): CreativeMarkdownResume {
    return new CreativeMarkdownResume() // CreativeMarkdownResume implements MarkdownResume
  }

  createJSONResume(): CreativeJSONResume {
    return new CreativeJSONResume() // CreativeJSONResume implements JSONResume
  }
}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

JSON 履歴書 :

履歴書/json/CreativeJSONResume

// User inputs...
let theme = "minimalist"
let format = "pdf"

let factory: ResumeFactory

switch (theme) {
  case "minimalist":
    factory = new MinimalistResumeFactory()
    break
  case "modern":
    factory = new ModernResumeFactory()
    break
  case "creative":
    factory = new CreativeResumeFactory()
    break
  default:
    throw new Error("Invalid theme.")
}

const userInput = await getUserInput()
let resume

switch (format) {
  case "pdf":
    resume = factory.createPDFResume()
    break
  case "markdown":
    resume = factory.createMarkdownResume()
    break
  case "json":
    resume = factory.createJSONResume()
    break
  default:
    throw new Error("Invalid format.")
}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ミニマリスト履歴書工場のコンクリート製品

次に、MinimalistResumeFactory によって返される前の ConcreteProducts

を作成しましょう。

PDF 履歴書 :

履歴書/pdf/MinimalistPDFResume

npm install
npm start

ログイン後にコピー

マークダウンの再開 :

再開/マークダウン/MinimalistMarkdownResume

export type ResumeData = {
  name: string
  email: string
  phone: string
  experience: Experience[]
}

export type Experience = {
  company: string
  position: string
  startDate: string
  endDate: string
  description: string
}

ログイン後にコピー

JSON 履歴書 :

履歴書/json/MinimalistJSONResume

import { JSONResume } from "../resumes/json/JSONResume"
import { MarkdownResume } from "../resumes/markdown/MarkdownResume"
import { PDFResume } from "../resumes/pdf/PdfResume"

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

ログイン後にコピー

現代の履歴書工場のコンクリート製品

最後に、ModernResumeFactory によって返される前の ConcreteProducts

を作成しましょう。

PDF 履歴書 :

履歴書/pdf/ModernPDFResume

import * as fs from "fs/promises"

import { ResumeData } from "../../interfaces/Types"

export abstract class JSONResume {
  protected data!: ResumeData & { style: string }

  abstract generate(data: ResumeData): void

  async saveToFile(fileName: string): Promise<void> {
    await fs.writeFile(fileName, JSON.stringify(this.data, null, 2))
  }

  getData(): any {
    return this.data
  }
}

ログイン後にコピー

マークダウンの再開 :

再開/マークダウン/ModernMarkdownResume

import * as fs from "fs/promises"

import { ResumeData } from "../../interfaces/Types"

export abstract class MarkdownResume {
  protected content: string = ""

  abstract generate(data: ResumeData): void

  async saveToFile(fileName: string): Promise<void> {
    await fs.writeFile(fileName, this.content)
  }

  getContent(): string {
    return this.content
  }
}

ログイン後にコピー

JSON 履歴書 :

履歴書/json/ModernJSONResume

import * as fs from "fs"

import PDFDocument from "pdfkit"

import { ResumeData } from "../../interfaces/Types"

export abstract class PDFResume {
  protected doc: PDFKit.PDFDocument

  constructor() {
    this.doc = new PDFDocument()
  }

  abstract generate(data: ResumeData): void

  async saveToFile(fileName: string): Promise<void> {
    const stream = fs.createWriteStream(fileName)
    this.doc.pipe(stream)
    this.doc.end()

    await new Promise<void>((resolve, reject) => {
      stream.on("finish", resolve)
      stream.on("error", reject)
    })
  }

  getBuffer(): Buffer {
    return this.doc.read() as Buffer
  }
}

ログイン後にコピー

Index.ts ファイルでのファクトリーの使用

クライアント コードでファクトリーを使用して、これまでの作業の成果を実らせてみましょう。

ファクトリーを使用するだけで、非常にクリーンな方法で履歴書ビルダー ライブラリを使用できるようになりました。

ユーザーが提供する必要があるのは次の 2 つだけです:

  1. 製品タイプ : どのようなタイプの PDF を作成したいですか?
  2. テーマ : 彼はどのような履歴書のスタイルを好みますか?

index.ts

import { ResumeFactory } from "../interfaces/ResumeFactory"
import { CreativeJSONResume } from "../resumes/json/CreativeJSONResume"
import { CreativeMarkdownResume } from "../resumes/markdown/CreativeMarkdownResume"
import { CreativePDFResume } from "../resumes/pdf/CreativePDFResume"

export class CreativeResumeFactory implements ResumeFactory {
  createPDFResume(): CreativePDFResume {
    return new CreativePDFResume() // CreativePDFResume extends PDFResume
  }

  createMarkdownResume(): CreativeMarkdownResume {
    return new CreativeMarkdownResume() // CreativeMarkdownResume extends MarkdownResume
  }

  createJSONResume(): CreativeJSONResume {
    return new CreativeJSONResume() // CreativeJSONResume extends JSONResume
  }
}

ログイン後にコピー

上記のコードは 3 つのステップで動作します:

  1. ユーザー入力: まず、テーマフォーマット の値を取得します。
  2. ファクトリの選択 : 次に、テーマ の値に基づいて、対応するファクトリをインスタンス化します。
  3. プロダクトの作成 : 最後に、選択した形式に応じて、対応する ファクトリー メソッド を呼び出します。

ユーザーは、製品とそれに対応するバリエーションがどのように作成されるかには関心がありません。 テーマフォーマット を選択するだけで済みます。対応する製品がリクエストに応じて作成されます。

クライアント コードは変更に対して堅牢になりました。新しいテーマやスタイルを追加したい場合は、それを担当する新しいファクトリーを作成するだけです。

チョーク ライブラリを使用して、セマンティックな意味に応じてターミナル ログに色を付けました。

CLI アプリのユーザーから入力を取得できるようにするために、inquirer パッケージを使用しました。これは、ユーザーからさまざまなタイプの入力を取得するための非常に魅力的でユーザーフレンドリーな方法を提供します。

  1. getUserInput 関数は、名前、電子メール、電話番号などの履歴書の主な情報を取得するために使用されました。
  2. getExperience ユーティリティ関数は、ユーザーからエクスペリエンス情報を再帰的に取得するために使用されました。つまり、最初のエントリのエクスペリエンス情報を入力するようユーザーに求め、次に追加する別のエクスペリエンスがあるかどうかを尋ねます。答えが「いいえ」の場合、関数は単に戻ります。一方、「はい」を選択すると、次の体験の情報を入力するよう再度求められます。

utils/userInput

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

結論

Abstract Factory パターンは、ソフトウェア設計者と開発者の武器となる強力なツールです。これは、具体的なクラスを指定せずに関連オブジェクトのファミリーを作成するための構造化されたアプローチを提供します。このパターンは、次の場合に特に役立ちます。

  1. システムは、その製品がどのように作成、構成、表現されるかに依存しない必要があります。
  2. システムは、複数の製品ファミリーのいずれかを使用して構成する必要があります。
  3. 関連する製品オブジェクトのファミリーは一緒に使用するように設計されており、この制約を強制する必要があります。
  4. 製品のクラス ライブラリを提供したいと考えており、その実装ではなくインターフェイスのみを公開したいと考えています。

私たちの実践的な例では、Abstract Factory パターンを適用して柔軟で拡張可能な履歴書生成システムを作成する方法を確認しました。このシステムは、既存のコードを変更することなく、新しい履歴書のスタイルや出力形式に簡単に対応でき、オープン/クローズの原則が実際に機能していることを実証します。

Abstract Factory パターンには多くの利点がありますが、コードベースがさらに複雑になる可能性があることに注意することが重要です。したがって、それが提供する柔軟性が特定の使用例に必要かどうかを評価することが重要です。

Abstract Factory のようなデザイン パターンをマスターすると、堅牢で柔軟性があり、保守可能なソフトウェア システムを作成するための準備が整います。これらのパターンを探索してプロジェクトに適用し続けて、ソフトウェア設計スキルを向上させてください。

接触

ご質問がある場合、またはさらに話し合いたい場合は、お気軽にここからご連絡ください。

コーディングを楽しんでください!

以上が抽象ファクトリー パターンをマスターする: 包括的なガイドの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート