0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

個人的アウトプットカレンダーAdvent Calendar 2024

Day 21

【TypeScript】デザインパターン実装 ~構造に関するパターン編~

Last updated at Posted at 2024-12-22

今回はGoFのデザインパターンのうち、
「構造」に関するパターンをTypeScriptで実装してみます。

GoFのデザインパターンとは

そもそもデザインパターンとは何でしょうか。

Wikipediaには下記のように記述されています。

ソフトウェア開発におけるデザインパターンまたは設計パターン(英: design pattern)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。

つまり、数々の経験によって生み出された「このように設計しておくと便利である」という設計をパターン化したものです。


GoFのデザインパターンとは、
Gang of Four と呼ばれる4人組によって設計されたデザインパターンのことを指します。

このデザインパターンは役割に基づいてさらに3つのパターンに分類されています。

今回はそのうちの「構造」に関するパターンをTypeScriptで実装していきます。

「構造」デザインパターン実装

Adapter

Adapter は、既存のクラスのインターフェースを別のインターフェースへ変換するために使用されます。

既存のコードを変更することなく、新しいコードやシステムと統合することができます。

// 既存クラス
class OldSystem {
  oldMethod(): string {
    return "Old System Method";
  }
}

// 新しいインターフェース
interface NewSystem {
  newMethod(): string;
}

// Adapterクラス
class Adapter implements NewSystem {
  private oldSystem: OldSystem;

  constructor(oldSystem: OldSystem) {
    this.oldSystem = oldSystem;
  }

  newMethod(): string {
    return this.oldSystem.oldMethod();
  }
}

// 使用例
const oldSystem = new OldSystem();
const adapter = new Adapter(oldSystem);
console.log(adapter.newMethod()); // "Old System Method"

Bridge

Bridge は、抽象部分と実装部分を分離し、独立して変更できるようにします。

抽象部分と実装部分を独立して変更できるため、
新しい機能や実装を容易に追加することができます。

// 実装のインターフェース
interface Renderer {
  renderCircle(radius: number): void;
}

// 具体的な実装
class SVGRenderer implements Renderer {
  renderCircle(radius: number): void {
    console.log(`<circle r="${radius}" />`);
  }
}

class CanvasRenderer implements Renderer {
  renderCircle(radius: number): void {
    console.log(`Canvas circle with radius ${radius}`);
  }
}

// 抽象部分
class Shape {
  protected renderer: Renderer;

  constructor(renderer: Renderer) {
    this.renderer = renderer;
  }

  draw(): void {}
}

// 具体的な抽象クラス
class Circle extends Shape {
  private radius: number;

  constructor(renderer: Renderer, radius: number) {
    super(renderer);
    this.radius = radius;
  }

  draw(): void {
    this.renderer.renderCircle(this.radius);
  }
}

// 使用例
const svgCircle = new Circle(new SVGRenderer(), 10);
svgCircle.draw(); // "<circle r="10" />"

const canvasCircle = new Circle(new CanvasRenderer(), 20);
canvasCircle.draw(); // "Canvas circle with radius 20"

Composite

Composite では、オブジェクトをツリー構造にして、個々のオブジェクトとその集合を同一視します。

ツリー構造を通じて、ネストされたオブジェクトを簡単に操作することができます。

// コンポーネントのインターフェース
interface Component {
  getName(): string;
  operation(): string;
}

// 個々の要素 (Leaf)
class Leaf implements Component {
  private name: string;

  constructor(name: string) {
    this.name = name;
  }

  getName(): string {
    return this.name;
  }

  operation(): string {
    return `Leaf: ${this.name}`;
  }
}

// コンテナ (Composite)
class Composite implements Component {
  private children: Component[] = [];

  add(component: Component): void {
    this.children.push(component);
  }

  remove(component: Component): void {
    this.children = this.children.filter(child => child !== component);
  }

  getName(): string {
    return "Composite";
  }

  operation(): string {
    return `Composite(${this.children.map(child => child.operation()).join(", ")})`;
  }
}

// 使用例
const leaf1 = new Leaf("A");
const leaf2 = new Leaf("B");

const composite = new Composite();
composite.add(leaf1);
composite.add(leaf2);

console.log(composite.operation()); // "Composite(Leaf: A, Leaf: B)"

Decorator

Decorator は、オブジェクトの機能を動的に追加または修飾します。

既存のクラスを修正せずに拡張することができます。

// 基本コンポーネント
interface Coffee {
  getDescription(): string;
  cost(): number;
}

class BasicCoffee implements Coffee {
  getDescription(): string {
    return "Basic Coffee";
  }
  cost(): number {
    return 200;
  }
}

// デコレータ
abstract class CoffeeDecorator implements Coffee {
  protected decoratedCoffee: Coffee;

  constructor(coffee: Coffee) {
    this.decoratedCoffee = coffee;
  }

  getDescription(): string {
    return this.decoratedCoffee.getDescription();
  }

  cost(): number {
    return this.decoratedCoffee.cost();
  }
}

class MilkDecorator extends CoffeeDecorator {
  getDescription(): string {
    return `${this.decoratedCoffee.getDescription()}, Milk`;
  }

  cost(): number {
    return this.decoratedCoffee.cost() + 50;
  }
}

// 使用例
const basicCoffee = new BasicCoffee();
const milkCoffee = new MilkDecorator(basicCoffee);
console.log(milkCoffee.getDescription()); // "Basic Coffee, Milk"
console.log(milkCoffee.cost()); // 250

Facade

Facade は、複雑なサブシステムを簡単に使用できるようにするためのインターフェースを提供します。

クライアントとサブシステム間の結合度を下げ、コードの柔軟性を向上します。

class SubsystemA {
  operationA(): string {
    return "SubsystemA: Ready!";
  }
}

class SubsystemB {
  operationB(): string {
    return "SubsystemB: Go!";
  }
}

class Facade {
  private subsystemA: SubsystemA;
  private subsystemB: SubsystemB;

  constructor() {
    this.subsystemA = new SubsystemA();
    this.subsystemB = new SubsystemB();
  }

  operation(): string {
    const resultA = this.subsystemA.operationA();
    const resultB = this.subsystemB.operationB();
    return `${resultA} ${resultB}`;
  }
}

// 使用例
const facade = new Facade();
console.log(facade.operation()); // "SubsystemA: Ready! SubsystemB: Go!"

Proxy

Proxy は、他のオブジェクトへのアクセスを制御する代理オブジェクトを提供します。

特定の条件下でのみオブジェクトにアクセスできるように制限することができます。

interface Service {
  request(): string;
}

class RealService implements Service {
  request(): string {
    return "RealService: Handling request.";
  }
}

class ProxyService implements Service {
  private realService: RealService;

  constructor(realService: RealService) {
    this.realService = realService;
  }

  request(): string {
    console.log("ProxyService: Logging before request...");
    const result = this.realService.request();
    console.log("ProxyService: Logging after request...");
    return result;
  }
}

// 使用例
const realService = new RealService();
const proxy = new ProxyService(realService);
console.log(proxy.request());
// "ProxyService: Logging before request..."
// "RealService: Handling request."
// "ProxyService: Logging after request..."

おわりに

今回は「構造」に関するデザインパターンをTypeScriptで実装しました。
次回は「振る舞い」に関するパターンを記載していこうと思います。

それでは。

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?