アプリケーション内でロジックを何度も複製せずに、オブジェクトのさまざまなファミリーの複数のバリエーションを作成する必要があると感じたことはありますか?
あるいは、アプリケーションを構築した後で、新しい要件やクライアントの設定の変更により、まったく新しいオブジェクトが必要になり、コードベース全体の作り直しが必要になったことに気づいたということもあるでしょうか?
既存のコードを壊すことなく、新しい実装をプラグインするだけで、シームレスに新しいバリエーションを導入できる方法があったとしたらどうでしょうか?
そこで、Abstract Factory デザイン パターンが登場します!
このチュートリアルでは、複数の形式とテーマをサポートする複数の種類の履歴書を作成するための Node.js CLI アプリケーション を構築することで、この強力なデザイン パターンを詳しく説明します。
Abstract Factory は 創造的なデザイン パターン であり、新しいキーワードまたは演算子。
問題Abstract Factory 設計パターンは、このブログ記事で説明したファクトリ メソッド設計パターンを一般化したものと考えることができます。
Abstract Factory デザイン パターンは、次の問題を解決します。
Abstract Factory デザイン パターンは、製品の種類ごとにインターフェイスまたは抽象クラスを宣言することで、これらの問題を解決します。
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
抽象ファクトリーを作成します。これは、あらゆるタイプの製品を作成するファクトリー メソッドを宣言するインターフェイスです。
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 }
Abstract Factory デザイン パターンの構造は次のクラスで構成されます:
この場合、Factory で宣言されたファクトリ メソッドは次のとおりです: createProductA と createProductB
ConcretProductA1 と ConcretProductA2 は IProductA を実装します。 ConcretProductB1 と ConcretProductB2 は IProductB を実装します
このセクションでは、ユーザーが選択したテーマと形式に基づいて履歴書を作成する、完全に動作する 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 }
次に、ジェネリック ファクトリ タイプを宣言しましょう。これは、サポートされているさまざまな製品タイプに対応する 3 つの ファクトリ メソッド を定義します: PDFResume 、MarkdownResume 、および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 } }
次のセクションでコードについて説明します。
次に、汎用製品クラスの作成に進みましょう。
対応するサブタイプ間で属性とメソッドの両方を共有したいため、すべての製品タイプは抽象クラスになります。
クラスは以下を定義します:
履歴書/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 は、クラスがインスタンス化できないジェネリック型であることを意味します。他のクラスによってのみ継承できます。
クラスは以下を定義します:
再開/マークダウン/MarkdownResume
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
クラスには、 PDFKit.PDFDocument 型の保護された doc オブジェクトがあり、これは pdfkit というライブラリからインポートされます。このライブラリは、オブジェクト指向インターフェイスを通じて PDF ドキュメントの作成と操作を簡素化します。
クラスは以下を定義します:
履歴書/pdf/PDF履歴書
export interface ResumeFactory { createPDFResume(): PDFResume createMarkdownResume(): MarkdownResume createJSONResume(): JSONResume }
汎用製品タイプ と 抽象ファクトリー を定義したので、次に、ConcreteFactories の作成に進みます。すべてのジェネリック製品タイプの異なるバリエーション。
履歴書には、クリエイティブ、ミニマリスト、および モダンの 3 つのバリエーションがあります。そして、3 種類の汎用プロダクト: JSON 、 PDF 、および Markdown。
抽象ファクトリ (ResumeFactory) は、製品の作成を担当する 3 つのファクトリ メソッドを定義します。
製品ごとに複数のバリエーションをサポートするには、3 つのコンクリート工場を作成する必要があります。
各コンクリート工場は、独自の味を持つ 3 種類の製品を作成します。
ファクトリー/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 } }
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.") }
factory/ModernResumeFactory
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
次に、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 } }
クライアント コードでファクトリーを使用して、これまでの作業の成果を実らせてみましょう。
ファクトリーを使用するだけで、非常にクリーンな方法で履歴書ビルダー ライブラリを使用できるようになりました。
ユーザーが提供する必要があるのは次の 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 つのステップで動作します:
ユーザーは、製品とそれに対応するバリエーションがどのように作成されるかには関心がありません。 テーマ と フォーマット を選択するだけで済みます。対応する製品がリクエストに応じて作成されます。
クライアント コードは変更に対して堅牢になりました。新しいテーマやスタイルを追加したい場合は、それを担当する新しいファクトリーを作成するだけです。
チョーク ライブラリを使用して、セマンティックな意味に応じてターミナル ログに色を付けました。
CLI アプリのユーザーから入力を取得できるようにするために、inquirer パッケージを使用しました。これは、ユーザーからさまざまなタイプの入力を取得するための非常に魅力的でユーザーフレンドリーな方法を提供します。
utils/userInput
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
Abstract Factory パターンは、ソフトウェア設計者と開発者の武器となる強力なツールです。これは、具体的なクラスを指定せずに関連オブジェクトのファミリーを作成するための構造化されたアプローチを提供します。このパターンは、次の場合に特に役立ちます。
私たちの実践的な例では、Abstract Factory パターンを適用して柔軟で拡張可能な履歴書生成システムを作成する方法を確認しました。このシステムは、既存のコードを変更することなく、新しい履歴書のスタイルや出力形式に簡単に対応でき、オープン/クローズの原則が実際に機能していることを実証します。
Abstract Factory パターンには多くの利点がありますが、コードベースがさらに複雑になる可能性があることに注意することが重要です。したがって、それが提供する柔軟性が特定の使用例に必要かどうかを評価することが重要です。
Abstract Factory のようなデザイン パターンをマスターすると、堅牢で柔軟性があり、保守可能なソフトウェア システムを作成するための準備が整います。これらのパターンを探索してプロジェクトに適用し続けて、ソフトウェア設計スキルを向上させてください。
ご質問がある場合、またはさらに話し合いたい場合は、お気軽にここからご連絡ください。
コーディングを楽しんでください!
以上が抽象ファクトリー パターンをマスターする: 包括的なガイドの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。