JavaScriptにおけるデザインパターンの実装手法

概要

デザインパターンは主に三つのカテゴリに分類されます:

生成に関するパターン:ファクトリーメソッド、抽象ファクトリー、シングルトン、ビルダー、プロトタイプ。

構造に関するパターン:アダプター、デコレーター、プロキシー、ファサード、ブリッジ、コンポジット、フライウェイト。

振る舞いに関するパターン:ストラテジー、テンプレートメソッド、オブザーバー、イテレーター、チェーンオブレスポンシビリティ、コマンド、メメント、ステート、ビジター、メディエーター、インタープリター。

生成に関するパターン

ファクトリーメソッド(Factory Method)

ファクトリーメソッドは、インスタンス作成のためのインターフェースを定義し、どのクラスをインスタンス化するかをサブクラスで決定します。これにより、オブジェクトの作成と利用を分離できます。


class Vehicle {
  start() {
    throw new Error('Method must be implemented');
  }
}

class Car extends Vehicle {
  start() {
    return 'Car engine started';
  }
}

class Motorcycle extends Vehicle {
  start() {
    return 'Motorcycle engine started';
  }
}

class VehicleCreator {
  produce(type) {
    switch(type) {
      case 'car':
        return new Car();
      case 'motorcycle':
        return new Motorcycle();
      default:
        throw new Error(`Unsupported vehicle type: ${type}`);
    }
  }
}

const creator = new VehicleCreator();
const myCar = creator.produce('car');
console.log(myCar.start()); // Output: Car engine started
const myBike = creator.produce('motorcycle');
console.log(myBike.start()); // Output: Motorcycle engine started

抽象ファクトリー(Abstract Factory)

抽象ファクトリーは、関連するオブジェクト群を生成するための一貫したインターフェースを提供します。


class DeviceButton {
  render() {
    throw new Error('Render method required');
  }
}

class DeviceWindow {
  display() {
    throw new Error('Display method required');
  }
}

class WindowsButton extends DeviceButton {
  render() {
    return 'Windows styled button';
  }
}

class WindowsWindow extends DeviceWindow {
  display() {
    return 'Windows styled window';
  }
}

class MacButton extends DeviceButton {
  render() {
    return 'Mac styled button';
  }
}

class MacWindow extends DeviceWindow {
  display() {
    return 'Mac styled window';
  }
}

class UIFactory {
  createButton() {
    throw new Error('Create button method required');
  }
  
  createWindow() {
    throw new Error('Create window method required');
  }
}

class WindowsUIFactory extends UIFactory {
  createButton() {
    return new WindowsButton();
  }
  
  createWindow() {
    return new WindowsWindow();
  }
}

class MacUIFactory extends UIFactory {
  createButton() {
    return new MacButton();
  }
  
  createWindow() {
    return new MacWindow();
  }
}

const winFactory = new WindowsUIFactory();
console.log(winFactory.createButton().render()); // Output: Windows styled button
console.log(winFactory.createWindow().display()); // Output: Windows styled window

const macFactory = new MacUIFactory();
console.log(macFactory.createButton().render()); // Output: Mac styled button
console.log(macFactory.createWindow().display()); // Output: Mac styled window

シングルトン(Singleton)

シングルトンは、クラスのインスタンスが一つだけ存在することを保証し、グローバルなアクセスポイントを提供します。


class DatabaseConnection {
  constructor() {
    if (DatabaseConnection.instance) {
      return DatabaseConnection.instance;
    }
    
    this.connectionString = 'database://localhost:5432';
    this.isConnected = false;
    DatabaseConnection.instance = this;
    return this;
  }

  connect() {
    if (!this.isConnected) {
      this.isConnected = true;
      console.log('Database connected');
    }
    return this;
  }

  disconnect() {
    this.isConnected = false;
    console.log('Database disconnected');
  }
}

const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();
console.log(db1 === db2); // Output: true

db1.connect(); // Output: Database connected
db2.disconnect(); // Output: Database disconnected

ビルダー(Builder)

ビルダーは、複雑なオブジェクトを段階的に構築するためのパターンです。


class Computer {
  constructor() {
    this.parts = [];
  }
  
  addPart(part) {
    this.parts.push(part);
  }
  
  getSpecification() {
    return this.parts.join(' + ');
  }
}

class ComputerBuilder {
  constructor() {
    this.computer = new Computer();
  }
  
  reset() {
    this.computer = new Computer();
  }
  
  buildCPU(cpu) {
    this.computer.addPart(`CPU: ${cpu}`);
    return this;
  }
  
  buildRAM(ram) {
    this.computer.addPart(`RAM: ${ram}GB`);
    return this;
  }
  
  buildStorage(storage) {
    this.computer.addPart(`Storage: ${storage}TB`);
    return this;
  }
  
  getResult() {
    return this.computer;
  }
}

class ComputerDirector {
  constructGamingPC(builder) {
    return builder
      .reset()
      .buildCPU('Intel i7')
      .buildRAM(16)
      .buildStorage(1)
      .getResult();
  }
}

const director = new ComputerDirector();
const gamingPC = director.constructGamingPC(new ComputerBuilder());
console.log(gamingPC.getSpecification()); 
// Output: CPU: Intel i7 + RAM: 16GB + Storage: 1TB

プロトタイプ(Prototype)

プロトタイプは、既存のオブジェクトをコピーして新しいオブジェクトを作成するパターンです。


const prototypeObject = {
  type: 'vehicle',
  move() {
    console.log(`${this.type} is moving`);
  },
  stop() {
    console.log(`${this.type} has stopped`);
  }
};

const carInstance = Object.create(prototypeObject);
carInstance.type = 'car';
carInstance.move(); // Output: car is moving
carInstance.stop(); // Output: car has stopped

const truckInstance = Object.create(prototypeObject);
truckInstance.type = 'truck';
truckInstance.move(); // Output: truck is moving
truckInstance.stop(); // Output: truck has stopped

構造に関するパターン

アダプター(Adapter)

アダプターは、互換性のないインターフェースを持つオブジェクト同士を接続できるようにします。


class LegacyPrinter {
  oldPrint(text) {
    console.log(`Legacy print: ${text}`);
  }
}

class ModernPrinterInterface {
  print(content) {
    throw new Error('Print method must be implemented');
  }
}

class PrinterAdapter extends ModernPrinterInterface {
  constructor(legacyPrinter) {
    super();
    this.legacyPrinter = legacyPrinter;
  }
  
  print(content) {
    this.legacyPrinter.oldPrint(content);
  }
}

const legacyPrinter = new LegacyPrinter();
const modernPrinter = new PrinterAdapter(legacyPrinter);
modernPrinter.print('Modern content'); // Output: Legacy print: Modern content

デコレーター(Decorator)

デコレーターは、他のオブジェクトを変更せずに機能を動的に追加できます。


class Coffee {
  cost() {
    return 5;
  }
  
  description() {
    return 'Coffee';
  }
}

class CoffeeDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }
  
  cost() {
    return this.coffee.cost();
  }
  
  description() {
    return this.coffee.description();
  }
}

class MilkDecorator extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 1.5;
  }
  
  description() {
    return `${this.coffee.description()}, Milk`;
  }
}

class SugarDecorator extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 0.5;
  }
  
  description() {
    return `${this.coffee.description()}, Sugar`;
  }
}

let coffee = new Coffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);

console.log(coffee.description()); // Output: Coffee, Milk, Sugar
console.log(coffee.cost()); // Output: 7

プロキシー(Proxy)

プロキシーは、オブジェクトへのアクセスを制御するための中継役として機能します。


class RealImage {
  constructor(filename) {
    this.filename = filename;
    this.loadFromDisk();
  }
  
  loadFromDisk() {
    console.log(`Loading image: ${this.filename}`);
  }
  
  display() {
    console.log(`Displaying image: ${this.filename}`);
  }
}

class ImageProxy {
  constructor(filename) {
    this.filename = filename;
    this.realImage = null;
  }
  
  display() {
    if (!this.realImage) {
      this.realImage = new RealImage(this.filename);
    }
    this.realImage.display();
  }
}

const image = new ImageProxy('photo.jpg');
image.display(); // Output: Loading image: photo.jpg, Displaying image: photo.jpg
image.display(); // Output: Displaying image: photo.jpg (no loading again)

ファサード(Facade)

ファサードは、複雑なサブシステムに対する単純なインターフェースを提供します。


class AudioPlayer {
  playAudio(file) {
    console.log(`Playing audio: ${file}`);
  }
}

class VideoPlayer {
  playVideo(file) {
    console.log(`Playing video: ${file}`);
  }
}

class SubtitleManager {
  loadSubtitles(file) {
    console.log(`Loading subtitles for: ${file}`);
  }
}

class MediaPlayerFacade {
  constructor() {
    this.audio = new AudioPlayer();
    this.video = new VideoPlayer();
    this.subtitle = new SubtitleManager();
  }
  
  playMedia(filename) {
    this.audio.playAudio(filename);
    this.video.playVideo(filename);
    this.subtitle.loadSubtitles(filename);
  }
}

const player = new MediaPlayerFacade();
player.playMedia('movie.mp4');
// Output: Playing audio: movie.mp4
// Output: Playing video: movie.mp4
// Output: Loading subtitles for: movie.mp4

ブリッジ(Bridge)

ブリッジは、抽象化と実装を分離し、それぞれが独立して変更可能にします。


class Theme {
  apply() {
    throw new Error('Apply method must be implemented');
  }
}

class DarkTheme extends Theme {
  apply() {
    return 'Dark theme applied';
  }
}

class LightTheme extends Theme {
  apply() {
    return 'Light theme applied';
  }
}

class Application {
  constructor(theme) {
    this.theme = theme;
  }
  
  changeTheme(theme) {
    this.theme = theme;
  }
}

class WebApplication extends Application {
  run() {
    console.log(`Web app running with ${this.theme.apply()}`);
  }
}

class MobileApplication extends Application {
  run() {
    console.log(`Mobile app running with ${this.theme.apply()}`);
  }
}

const dark = new DarkTheme();
const webApp = new WebApplication(dark);
webApp.run(); // Output: Web app running with Dark theme applied

const light = new LightTheme();
webApp.changeTheme(light);
webApp.run(); // Output: Web app running with Light theme applied

コンポジット(Composite)

コンポジットは、部分と全体の階層構造を表現し、個々のオブジェクトとオブジェクトのグループを同じように扱えるようにします。


class FileSystemItem {
  constructor(name) {
    this.name = name;
  }
  
  getName() {
    return this.name;
  }
  
  getSize() {
    throw new Error('GetSize method must be implemented');
  }
}

class File extends FileSystemItem {
  constructor(name, size) {
    super(name);
    this.size = size;
  }
  
  getSize() {
    return this.size;
  }
}

class Directory extends FileSystemItem {
  constructor(name) {
    super(name);
    this.children = [];
  }
  
  add(item) {
    this.children.push(item);
  }
  
  remove(item) {
    const index = this.children.indexOf(item);
    if (index > -1) {
      this.children.splice(index, 1);
    }
  }
  
  getSize() {
    return this.children.reduce((total, item) => total + item.getSize(), 0);
  }
}

const rootDir = new Directory('Root');
const file1 = new File('Document.txt', 1024);
const file2 = new File('Image.png', 2048);
const subDir = new Directory('Subfolder');

subDir.add(file2);
rootDir.add(file1);
rootDir.add(subDir);

console.log(rootDir.getSize()); // Output: 3072 (1024 + 2048)

フライウェイト(Flyweight)

フライウェイトは、多くの類似オブジェクトのメモリ使用量を削減するために共有を活用します。


class CharacterFlyweight {
  constructor(char, font, size) {
    this.character = char;
    this.font = font;
    this.size = size;
  }
  
  render(position) {
    console.log(`Rendering '${this.character}' at position ${position.x},${position.y} with ${this.font}, size ${this.size}`);
  }
}

class FlyweightFactory {
  constructor() {
    this.pool = new Map();
  }
  
  getFlyweight(char, font, size) {
    const key = `${char}-${font}-${size}`;
    
    if (!this.pool.has(key)) {
      this.pool.set(key, new CharacterFlyweight(char, font, size));
    }
    
    return this.pool.get(key);
  }
  
  getPoolSize() {
    return this.pool.size;
  }
}

const factory = new FlyweightFactory();
const charA = factory.getFlyweight('A', 'Arial', 12);
const charB = factory.getFlyweight('A', 'Arial', 12); // Same instance as charA

console.log(charA === charB); // Output: true
charA.render({x: 10, y: 20}); // Rendering 'A' at position 10,20 with Arial, size 12

振る舞いに関するパターン

ストラテジー(Strategy)

ストラテジーは、アルゴリズム群を定義し、それらを交換可能にします。


class PaymentProcessor {
  constructor(strategy) {
    this.paymentStrategy = strategy;
  }
  
  process(amount) {
    return this.paymentStrategy.pay(amount);
  }
  
  setStrategy(strategy) {
    this.paymentStrategy = strategy;
  }
}

class CreditCardPayment {
  pay(amount) {
    return `Paid $${amount} via credit card`;
  }
}

class PayPalPayment {
  pay(amount) {
    return `Paid $${amount} via PayPal`;
  }
}

const processor = new PaymentProcessor(new CreditCardPayment());
console.log(processor.process(100)); // Output: Paid $100 via credit card

processor.setStrategy(new PayPalPayment());
console.log(processor.process(100)); // Output: Paid $100 via PayPal

テンプレートメソッド(Template Method)

テンプレートメソッドは、アルゴリズムの骨組みを定義し、一部のステップをサブクラスで実装可能にします。


class DataProcessor {
  execute(data) {
    const validatedData = this.validate(data);
    const processedData = this.process(validatedData);
    return this.finalize(processedData);
  }
  
  validate(data) {
    throw new Error('Validate method must be implemented');
  }
  
  process(data) {
    throw new Error('Process method must be implemented');
  }
  
  finalize(data) {
    throw new Error('Finalize method must be implemented');
  }
}

class JSONProcessor extends DataProcessor {
  validate(data) {
    console.log('Validating JSON data');
    return data;
  }
  
  process(data) {
    console.log('Processing JSON data');
    return JSON.stringify(data);
  }
  
  finalize(data) {
    console.log('Finalizing JSON processing');
    return `JSON Result: ${data}`;
  }
}

class XMLProcessor extends DataProcessor {
  validate(data) {
    console.log('Validating XML data');
    return data;
  }
  
  process(data) {
    console.log('Processing XML data');
    return `<xml>${data}</xml>`;
  }
  
  finalize(data) {
    console.log('Finalizing XML processing');
    return `XML Result: ${data}`;
  }
}

const jsonProc = new JSONProcessor();
console.log(jsonProc.execute({name: 'John'}));
// Output: Validating JSON data, Processing JSON data, Finalizing JSON processing, JSON Result: {"name":"John"}

const xmlProc = new XMLProcessor();
console.log(xmlProc.execute('<data>content</data>'));
// Output: Validating XML data, Processing XML data, Finalizing XML processing, XML Result: <xml><data>content</data></xml>

オブザーバー(Observer)

オブジェクト間の1対多の依存関係を定義し、あるオブジェクトの状態が変化すると、すべての依存オブジェクトが自動的に通知されます。


class NewsAgency {
  constructor() {
    this.subscribers = [];
    this.news = '';
  }
  
  addSubscriber(subscriber) {
    this.subscribers.push(subscriber);
  }
  
  removeSubscriber(subscriber) {
    const index = this.subscribers.indexOf(subscriber);
    if (index > -1) {
      this.subscribers.splice(index, 1);
    }
  }
  
  setNews(news) {
    this.news = news;
    this.notifySubscribers();
  }
  
  notifySubscribers() {
    this.subscribers.forEach(subscriber => subscriber.inform(this.news));
  }
}

class NewsChannel {
  inform(news) {
    console.log(`News Channel received: ${news}`);
  }
}

class Newspaper {
  inform(news) {
    console.log(`Newspaper received: ${news}`);
  }
}

const agency = new NewsAgency();
const channel = new NewsChannel();
const paper = new Newspaper();

agency.addSubscriber(channel);
agency.addSubscriber(paper);
agency.setNews('Breaking news: New JavaScript feature released!');
// Output: News Channel received: Breaking news: New JavaScript feature released!
// Output: Newspaper received: Breaking news: New JavaScript feature released!

イテレーター(Iterator)

イテレーターは、集合体の要素に順番にアクセスする手段を提供します。


class ListIterator {
  constructor(items) {
    this.items = items;
    this.position = 0;
  }
  
  hasNext() {
    return this.position < this.items.length;
  }
  
  next() {
    if (this.hasNext()) {
      return this.items[this.position++];
    }
    return null;
  }
  
  reset() {
    this.position = 0;
  }
}

class NumberList {
  constructor() {
    this.numbers = [];
  }
  
  addItem(number) {
    this.numbers.push(number);
  }
  
  createIterator() {
    return new ListIterator(this.numbers);
  }
}

const list = new NumberList();
list.addItem(1);
list.addItem(2);
list.addItem(3);

const iterator = list.createIterator();
while (iterator.hasNext()) {
  console.log(iterator.next()); // Output: 1, 2, 3
}

チェーンオブレスポンシビリティ(Chain of Responsibility)

リクエストを処理できるオブジェクトのチェーンを構築し、リクエストを順番に処理させます。


class Handler {
  constructor(nextHandler = null) {
    this.next = nextHandler;
  }
  
  handle(request) {
    if (this.canHandle(request)) {
      return this.process(request);
    } else if (this.next) {
      return this.next.handle(request);
    }
    return `Request ${request} was not handled`;
  }
  
  canHandle(request) {
    throw new Error('CanHandle method must be implemented');
  }
  
  process(request) {
    throw new Error('Process method must be implemented');
  }
}

class LevelOneSupport extends Handler {
  canHandle(request) {
    return request.priority <= 1;
  }
  
  process(request) {
    return `Level 1 support handled request: ${request.message}`;
  }
}

class LevelTwoSupport extends Handler {
  canHandle(request) {
    return request.priority <= 2;
  }
  
  process(request) {
    return `Level 2 support handled request: ${request.message}`;
  }
}

class LevelThreeSupport extends Handler {
  canHandle(request) {
    return request.priority <= 3;
  }
  
  process(request) {
    return `Level 3 support handled request: ${request.message}`;
  }
}

const level1 = new LevelOneSupport();
const level2 = new LevelTwoSupport(level1);
const level3 = new LevelThreeSupport(level2);

const request1 = { message: 'Password reset', priority: 1 };
const request2 = { message: 'System down', priority: 3 };
const request3 = { message: 'Feature request', priority: 5 };

console.log(level3.handle(request1)); // Output: Level 1 support handled request: Password reset
console.log(level3.handle(request2)); // Output: Level 3 support handled request: System down
console.log(level3.handle(request3)); // Output: Request [object Object] was not handled

コマンド(Command)

要求をオブジェクトとしてカプセル化し、異なるパラメータを使用してクライアントを要求から分離します。


class Command {
  execute() {
    throw new Error('Execute method must be implemented');
  }
  
  undo() {
    throw new Error('Undo method must be implemented');
  }
}

class Light {
  turnOn() {
    console.log('Light turned on');
  }
  
  turnOff() {
    console.log('Light turned off');
  }
}

class TurnOnCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }
  
  execute() {
    this.light.turnOn();
  }
  
  undo() {
    this.light.turnOff();
  }
}

class TurnOffCommand extends Command {
  constructor(light) {
    super();
    this.light = light;
  }
  
  execute() {
    this.light.turnOff();
  }
  
  undo() {
    this.light.turnOn();
  }
}

class RemoteControl {
  constructor() {
    this.commands = [];
    this.history = [];
  }
  
  setCommand(command) {
    this.commands.push(command);
  }
  
  pressButton(index) {
    if (index < this.commands.length) {
      const command = this.commands[index];
      command.execute();
      this.history.push(command);
    }
  }
  
  undoLast() {
    if (this.history.length > 0) {
      const lastCommand = this.history.pop();
      lastCommand.undo();
    }
  }
}

const livingRoomLight = new Light();
const turnOnCmd = new TurnOnCommand(livingRoomLight);
const turnOffCmd = new TurnOffCommand(livingRoomLight);

const remote = new RemoteControl();
remote.setCommand(turnOnCmd);
remote.setCommand(turnOffCmd);

remote.pressButton(0); // Output: Light turned on
remote.pressButton(1); // Output: Light turned off
remote.undoLast();     // Output: Light turned on

メメント(Memento)

オブジェクトの内部状態をキャプチャして外部に保存し、後で状態を復元できるようにします。


class EditorMemento {
  constructor(content) {
    this.content = content;
    this.timestamp = new Date();
  }
  
  getContent() {
    return this.content;
  }
  
  getTimestamp() {
    return this.timestamp;
  }
}

class Editor {
  constructor() {
    this.content = '';
  }
  
  write(text) {
    this.content += text;
  }
  
  save() {
    return new EditorMemento(this.content);
  }
  
  restore(memento) {
    this.content = memento.getContent();
  }
  
  getContent() {
    return this.content;
  }
}

class HistoryManager {
  constructor() {
    this.history = [];
  }
  
  save(memento) {
    this.history.push(memento);
  }
  
  restore(index) {
    if (index >= 0 && index < this.history.length) {
      return this.history[index];
    }
    return null;
  }
  
  getHistoryLength() {
    return this.history.length;
  }
}

const editor = new Editor();
const history = new HistoryManager();

editor.write('Initial content ');
history.save(editor.save());

editor.write('Additional content ');
history.save(editor.save());

console.log(editor.getContent()); // Output: Initial content Additional content

const firstState = history.restore(0);
if (firstState) {
  editor.restore(firstState);
  console.log(editor.getContent()); // Output: Initial content
}

ステート(State)

オブジェクトの内部状態が変化すると、その振る舞いも変化します。


class DocumentContext {
  constructor() {
    this.state = new DraftState(this);
  }
  
  setState(state) {
    this.state = state;
  }
  
  publish() {
    this.state.publish();
  }
  
  review() {
    this.state.review();
  }
}

class DocumentState {
  constructor(context) {
    this.context = context;
  }
  
  publish() {
    throw new Error('Publish method must be implemented');
  }
  
  review() {
    throw new Error('Review method must be implemented');
  }
}

class DraftState extends DocumentState {
  publish() {
    console.log('Document cannot be published from draft state');
  }
  
  review() {
    console.log('Sending document for review');
    this.context.setState(new ReviewState(this.context));
  }
}

class ReviewState extends DocumentState {
  publish() {
    console.log('Document cannot be published during review');
    this.context.setState(new DraftState(this.context));
  }
  
  review() {
    console.log('Document approved and published');
    this.context.setState(new PublishedState(this.context));
  }
}

class PublishedState extends DocumentState {
  publish() {
    console.log('Document already published');
  }
  
  review() {
    console.log('Published document sent back to draft');
    this.context.setState(new DraftState(this.context));
  }
}

const doc = new DocumentContext();
doc.review(); // Output: Sending document for review
doc.review(); // Output: Document approved and published
doc.publish(); // Output: Document already published
doc.review(); // Output: Published document sent back to draft
doc.publish(); // Output: Document cannot be published from draft state

ビジター(Visitor)

データ構造からアルゴリズムを分離し、データ構造を変更せずに新しい操作を追加できるようにします。


class Element {
  accept(visitor) {
    throw new Error('Accept method must be implemented');
  }
}

class ReportSection extends Element {
  constructor(title, content) {
    super();
    this.title = title;
    this.content = content;
  }
  
  accept(visitor) {
    visitor.visitReportSection(this);
  }
}

class ChartElement extends Element {
  constructor(type, data) {
    super();
    this.type = type;
    this.data = data;
  }
  
  accept(visitor) {
    visitor.visitChartElement(this);
  }
}

class DocumentStructure extends Element {
  constructor() {
    super();
    this.elements = [];
  }
  
  addElement(element) {
    this.elements.push(element);
  }
  
  accept(visitor) {
    visitor.visitDocumentStructure(this);
    this.elements.forEach(element => element.accept(visitor));
  }
}

class ReportVisitor {
  visitReportSection(section) {
    console.log(`Processing report section: ${section.title}`);
  }
  
  visitChartElement(chart) {
    console.log(`Processing chart: ${chart.type} with ${chart.data.length} data points`);
  }
  
  visitDocumentStructure(structure) {
    console.log('Processing document structure');
  }
}

const report = new DocumentStructure();
report.addElement(new ReportSection('Introduction', 'Intro content'));
report.addElement(new ChartElement('Bar', [1, 2, 3, 4, 5]));

const visitor = new ReportVisitor();
report.accept(visitor);
// Output: Processing document structure
// Output: Processing report section: Introduction
// Output: Processing chart: Bar with 5 data points

メディエーター(Mediator)

コンポーネント間の直接通信を避け、中央のメディエーターを通じてやり取りを行います。


class ChatRoom {
  constructor() {
    this.users = new Set();
  }
  
  addUser(user) {
    this.users.add(user);
    user.setChatRoom(this);
  }
  
  sendMessage(message, sender) {
    this.users.forEach(user => {
      if (user !== sender) {
        user.receiveMessage(message, sender);
      }
    });
  }
}

class User {
  constructor(name) {
    this.name = name;
    this.chatRoom = null;
  }
  
  setChatRoom(chatRoom) {
    this.chatRoom = chatRoom;
  }
  
  sendMessage(message) {
    console.log(`${this.name} sends: ${message}`);
    this.chatRoom.sendMessage(message, this);
  }
  
  receiveMessage(message, sender) {
    console.log(`${this.name} received from ${sender.name}: ${message}`);
  }
}

const chatRoom = new ChatRoom();
const alice = new User('Alice');
const bob = new User('Bob');
const charlie = new User('Charlie');

chatRoom.addUser(alice);
chatRoom.addUser(bob);
chatRoom.addUser(charlie);

alice.sendMessage('Hello everyone!'); 
// Output: Alice sends: Hello everyone!
// Output: Bob received from Alice: Hello everyone!
// Output: Charlie received from Alice: Hello everyone!

インタープリター(Interpreter)

言語の文法表現を定義し、その文法に基づいて文を解釈する手段を提供します。


class Expression {
  interpret(context) {
    throw new Error('Interpret method must be implemented');
  }
}

class NumberExpression extends Expression {
  constructor(value) {
    super();
    this.value = value;
  }
  
  interpret(context) {
    return this.value;
  }
}

class AddExpression extends Expression {
  constructor(left, right) {
    super();
    this.left = left;
    this.right = right;
  }
  
  interpret(context) {
    return this.left.interpret(context) + this.right.interpret(context);
  }
}

class SubtractExpression extends Expression {
  constructor(left, right) {
    super();
    this.left = left;
    this.right = right;
  }
  
  interpret(context) {
    return this.left.interpret(context) - this.right.interpret(context);
  }
}

class Parser {
  static parse(expressionString) {
    // Simple parser for expressions like "5 + 3" or "10 - 4"
    const tokens = expressionString.split(' ');
    
    if (tokens.length === 3) {
      const left = new NumberExpression(parseInt(tokens[0]));
      const right = new NumberExpression(parseInt(tokens[2]));
      
      if (tokens[1] === '+') {
        return new AddExpression(left, right);
      } else if (tokens[1] === '-') {
        return new SubtractExpression(left, right);
      }
    }
    
    throw new Error('Invalid expression format');
  }
}

const expression1 = Parser.parse('10 + 5');
console.log(expression1.interpret()); // Output: 15

const expression2 = Parser.parse('20 - 8');
console.log(expression2.interpret()); // Output: 12

タグ: javascript design-patterns factory-method singleton adapter

5月28日 08:07 投稿