今回はGoFのデザインパターンのうち、
「生成」に関するパターンをTypeScriptで実装してみます。
GoFのデザインパターンとは
そもそもデザインパターンとは何でしょうか。
Wikipediaには下記のように記述されています。
ソフトウェア開発におけるデザインパターンまたは設計パターン(英: design pattern)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。
つまり、数々の経験によって生み出された「このように設計しておくと便利である」という設計をパターン化したものです。
GoFのデザインパターンとは、
Gang of Four と呼ばれる4人組によって設計されたデザインパターンのことを指します。
このデザインパターンは役割に基づいてさらに3つのパターンに分類されています。
今回はそのうちの「生成」に関するパターンをTypeScriptで実装していきます。
「生成」デザインパターン実装
Singleton
Singleton
とは、インスタンスが1つだけであることを保証するデザインパターンです。
下記のようなメリットがあります
- グローバルアクセスを提供:クラスインスタンスをどこからでも利用可能
- リソースの節約:コストのかかるオブジェクトの作成を一回に制限できる
- データ一貫性の維持:状態の整合性が保たれる
下記の例では getInstance()
を呼び出すたびに同じインスタンスが返されます。
class Singleton {
private static instance: Singleton;
private constructor() {
console.log("Singleton instance created.");
}
static getInstance(): Singleton {
if (!this.instance) {
this.instance = new Singleton();
}
return this.instance;
}
public doSomething(): void {
console.log("Singleton logic executed.");
}
}
// 使用例
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
instance1.doSomething();
Factory Method
Factory Method
では、オブジェクトの作成をサブクラスに委ね、
インターフェースを通じてオブジェクトを生成します。
これによって、オブジェクトの生成部分をカプセル化し、
生成される具体的なクラスを変更してもクライアントコードに影響を与えません。
下記の例では、Creator
を介して製品を操作するため、具体的な製品クラスを意識する必要がありません。
// 製品のインターフェース
interface Product {
operation(): string;
}
// 具体的な製品クラス
class ConcreteProductA implements Product {
operation(): string {
return "ConcreteProductA";
}
}
class ConcreteProductB implements Product {
operation(): string {
return "ConcreteProductB";
}
}
// ファクトリメソッドを持つクラス
abstract class Creator {
abstract factoryMethod(): Product;
someOperation(): string {
const product = this.factoryMethod();
return `Creator: Working with ${product.operation()}`;
}
}
// 具体的なクリエイタークラス
class ConcreteCreatorA extends Creator {
factoryMethod(): Product {
return new ConcreteProductA();
}
}
class ConcreteCreatorB extends Creator {
factoryMethod(): Product {
return new ConcreteProductB();
}
}
// 使用例
const creatorA = new ConcreteCreatorA();
console.log(creatorA.someOperation()); // Creator: Working with ConcreteProductA
const creatorB = new ConcreteCreatorB();
console.log(creatorB.someOperation()); // Creator: Working with ConcreteProductB
Abstract Factory
Abstract Factory
では、関連する一連の部ジェクトを生成するためのインターフェースを提供し、具体的なクラスから独立させます。
下記の例では、製品の具体的なクラスからクライアントコードを分離しています。
// 抽象的な製品のインターフェース
interface Chair {
sitOn(): string;
}
interface Table {
placeItems(): string;
}
// 具体的な製品クラス
class ModernChair implements Chair {
sitOn(): string {
return "Sitting on a modern chair.";
}
}
class ModernTable implements Table {
placeItems(): string {
return "Items placed on a modern table.";
}
}
class VictorianChair implements Chair {
sitOn(): string {
return "Sitting on a Victorian chair.";
}
}
class VictorianTable implements Table {
placeItems(): string {
return "Items placed on a Victorian table.";
}
}
// 抽象ファクトリインターフェース
interface FurnitureFactory {
createChair(): Chair;
createTable(): Table;
}
// 具体的なファクトリクラス
class ModernFurnitureFactory implements FurnitureFactory {
createChair(): Chair {
return new ModernChair();
}
createTable(): Table {
return new ModernTable();
}
}
class VictorianFurnitureFactory implements FurnitureFactory {
createChair(): Chair {
return new VictorianChair();
}
createTable(): Table {
return new VictorianTable();
}
}
// 使用例
function createFurniture(factory: FurnitureFactory): void {
const chair = factory.createChair();
const table = factory.createTable();
console.log(chair.sitOn());
console.log(table.placeItems());
}
const modernFactory = new ModernFurnitureFactory();
createFurniture(modernFactory);
// Sitting on a modern chair.
// Items placed on a modern table.
const victorianFactory = new VictorianFurnitureFactory();
createFurniture(victorianFactory);
// Sitting on a Victorian chair.
// Items placed on a Victorian table.
Builder
Builder
は、複雑なオブジェクトの作成過程を分離し、異なる種類や表現でオブジェクトを構築できるようにします。
下記の例では、オブジェクトをステップごとに構築し、同じ構築過程で異なる表現が作成可能です。
// 製品クラス
class Product {
parts: string[] = [];
addPart(part: string): void {
this.parts.push(part);
}
showParts(): void {
console.log(`Product parts: ${this.parts.join(", ")}`);
}
}
// ビルダーインターフェース
interface Builder {
reset(): void;
buildPartA(): void;
buildPartB(): void;
getResult(): Product;
}
// 具体的なビルダー
class ConcreteBuilder implements Builder {
private product: Product;
constructor() {
this.product = new Product();
}
reset(): void {
this.product = new Product();
}
buildPartA(): void {
this.product.addPart("PartA");
}
buildPartB(): void {
this.product.addPart("PartB");
}
getResult(): Product {
return this.product;
}
}
// ディレクター
class Director {
private builder: Builder;
setBuilder(builder: Builder): void {
this.builder = builder;
}
constructMinimalProduct(): void {
this.builder.reset();
this.builder.buildPartA();
}
constructFullProduct(): void {
this.builder.reset();
this.builder.buildPartA();
this.builder.buildPartB();
}
}
// 使用例
const builder = new ConcreteBuilder();
const director = new Director();
director.setBuilder(builder);
director.constructMinimalProduct();
const minimalProduct = builder.getResult();
minimalProduct.showParts(); // Products parts: PartA
director.constructFullProduct();
const fullProduct = builder.getResult();
fullProduct.showParts(); // Products parts: PartB
Prototype
Prototype
では、既存のオブジェクトをコピーして新しいインスタンスを作成します。
下記の例では、clone()
メソッドを使用して、オリジナルと同じ状態持つ新しいオブジェクトを生成しています。
基のオブジェクトに影響を与えずに変更可能です。
// プロトタイプインターフェース
interface Prototype {
clone(): Prototype;
}
// 具体的なプロトタイプ
class ConcretePrototype implements Prototype {
public field: string;
constructor(field: string) {
this.field = field;
}
clone(): ConcretePrototype {
return new ConcretePrototype(this.field);
}
}
// 使用例
const prototype = new ConcretePrototype("Initial Value");
const cloned = prototype.clone();
console.log(prototype.field); // "Initial Value"
console.log(cloned.field); // "Initial Value"
cloned.field = "Modified Value";
console.log(prototype.field); // "Initial Value"
console.log(cloned.field); // "Modified Value"
おわりに
今回は「生成」に関するデザインパターンをTypeScriptで実装しました。
次回は「構造」に関するパターンを記載していこうと思います。
それでは。
参考文献