TypeScript中級

中級 TypeScriptで学ぶイベント駆動設計|アンチパターン編

導入

イベント駆動設計は、現代のアプリケーション開発において非常に重要なアプローチです。特に、ユーザーインターフェースや非同期処理が多く絡むシステムでは、イベントを中心に設計することで、柔軟性や拡張性を高めることが可能です。しかし、実際の開発現場では、イベント駆動設計におけるアンチパターンに遭遇することが少なくありません。本記事では、TypeScriptを用いた具体的なシナリオを通じて、よくある失敗例とその改善策を考察します。

教科書レベルの解説(イベント駆動設計)

重要な概念の整理

イベント駆動設計とは、システムの状態変化をイベントとして捉え、それに対する反応を定義する設計手法です。このアプローチでは、イベントをトリガーとして処理を行うため、コンポーネント間の結合度を低く保つことができ、モジュールの再利用性が向上します。

イベントの発行、リスニング、ハンドリングという3つの主要な要素が存在します。これらを適切に管理することで、システム全体の可読性と保守性が向上します。

コード例(TypeScript)


class EventEmitter {
    private events: { [key: string]: Function[] } = {};

    on(event: string, listener: Function) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(listener);
    }

    emit(event: string, ...args: any[]) {
        if (this.events[event]) {
            this.events[event].forEach(listener => listener(...args));
        }
    }
}

// 使用例
const emitter = new EventEmitter();
emitter.on('dataReceived', (data: string) => {
    console.log(`Received: ${data}`);
});
emitter.emit('dataReceived', 'Hello, World!');

コードの行ごとの解説

  1. class EventEmitter { – イベントを管理するクラスを定義。
  2. private events: { [key: string]: Function[] } = {}; – イベントとリスナーのマッピングを保持。
  3. on(event: string, listener: Function) { – 新しいリスナーを登録するメソッド。
  4. emit(event: string, ...args: any[]) { – 指定したイベントを発火し、リスナーを呼び出すメソッド。
  5. const emitter = new EventEmitter(); – EventEmitterのインスタンスを生成。
  6. emitter.on('dataReceived', (data: string) => {... – ‘dataReceived’イベントにリスナーを登録。
  7. emitter.emit('dataReceived', 'Hello, World!'); – ‘dataReceived’イベントを発火。

アンチパターン編

イベント駆動設計におけるよくあるアンチパターンの一つは、リスナーの管理が不適切な場合です。例えば、次のようなコードを考えてみましょう。


emitter.on('dataReceived', (data: string) => {
    console.log(`Processing: ${data}`);
    // ここに複雑なロジックが続く...
});

このコードでは、リスナー内にビジネスロジックが直接書かれており、リスナーが一つの責務を持っていないため、可読性が低下します。さらに、リスナーが多くなると、どのイベントがどのロジックを処理しているのかが不明瞭になります。

改善策としては、リスナーをシンプルに保ち、ビジネスロジックは別のサービスクラスに分離することが推奨されます。以下はその改善例です。


class DataProcessor {
    process(data: string) {
        console.log(`Processing: ${data}`);
        // ここにビジネスロジックを移動
    }
}

const processor = new DataProcessor();
emitter.on('dataReceived', (data: string) => processor.process(data));

このようにすることで、リスナーはイベントの受け取りに専念し、ビジネスロジックは別のクラスで管理されるため、コードの可読性と保守性が向上します。

まとめ

  • イベント駆動設計では、リスナーの役割を明確に分けることが重要。
  • ビジネスロジックをリスナー内に持ち込むことは避け、別のクラスに分離することで可読性が向上。
  • 適切な設計を行うことで、システム全体の保守性と拡張性が高まる。