JavaScriptのデザインパターン:Observerパターンでイベント駆動アーキテクチャを構築

JavaScriptのデザインパターン:Observerパターンでイベント駆動アーキテクチャを構築

Observerパターンは、オブジェクト間の依存関係を管理し、イベント駆動型アーキテクチャを実現するためのデザインパターンです。本記事では、JavaScriptでObserverパターンを活用して効率的なコードを構築する方法を説明します。

Observerパターンとは

Observerパターンは、あるオブジェクトの状態が変化した際に、その変化を他の関連オブジェクトに通知する仕組みです。

基本的なObserverの構造

基本的なObserverパターンの構造をコードで示します。

class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }

  notifyObservers(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    console.log('Observer received data:', data);
  }
}

Observerの登録と通知

Observerを登録し、イベント発生時に通知を送る例を示します。

const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notifyObservers('Event occurred');
// Observer received data: Event occurred
// Observer received data: Event occurred

Observerパターンの応用

イベント駆動型システムでの具体的な利用例を示します。

class EventManager {
  constructor() {
    this.events = {};
  }

  subscribe(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
  }

  unsubscribe(event, listener) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(l => l !== listener);
    }
  }

  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(listener => listener(data));
    }
  }
}

// 使用例
const eventManager = new EventManager();

function onUserLogin(data) {
  console.log('User logged in:', data);
}

eventManager.subscribe('login', onUserLogin);
eventManager.emit('login', { username: 'JohnDoe' });
// User logged in: { username: 'JohnDoe' }

DOMイベントリスナーとしてのObserverパターン

DOMイベントリスナーはObserverパターンの一種です。

document.getElementById('button').addEventListener('click', () => {
  console.log('Button clicked');
});

Observerパターンのカスタム実装

独自のイベントリスナーを作成する例です。

class CustomEventEmitter {
  constructor() {
    this.listeners = {};
  }

  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  }

  off(event, callback) {
    if (this.listeners[event]) {
      this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
    }
  }

  trigger(event, data) {
    if (this.listeners[event]) {
      this.listeners[event].forEach(callback => callback(data));
    }
  }
}

// 使用例
const emitter = new CustomEventEmitter();

function handleEvent(data) {
  console.log('Event handled with data:', data);
}

emitter.on('myEvent', handleEvent);
emitter.trigger('myEvent', { key: 'value' });
// Event handled with data: { key: 'value' }

Observerパターンのメリット

– コードの再利用性向上
– モジュール間の疎結合
– 拡張性の確保

Observerパターンのデメリット

– 複雑性の増加
– メモリリークのリスク

Async/Awaitとの組み合わせ

非同期処理を伴うObserverの例です。

class AsyncObserver {
  async update(data) {
    console.log('Async observer processing data:', data);
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log('Data processed');
  }
}

const asyncObserver = new AsyncObserver();
subject.addObserver(asyncObserver);
subject.notifyObservers('Async event');
// Async observer processing data: Async event
// (1秒後)
// Data processed

Observerパターンを利用する場面

– ユーザーインターフェイスの状態管理 – イベント駆動型プログラミング – データバインディング

他のデザインパターンとの比較

ObserverパターンとPub/Subパターンの違いについて考えます。

まとめ

Observerパターンを使用すると、イベント駆動型アーキテクチャの実装が容易になり、モジュール間の疎結合を実現できます。適切に使用することで、柔軟で保守性の高いアプリケーションを構築することが可能です。