Skip to content
⚠️ This article was written in 2019. Some content may be outdated.

JavaScriptのよく使うデザインパターン実践

デザインパターンはバックエンド専用ではありません——フロントエンドのコードでも広く使われています。これらのパターンをマスターすることで、コードがよりエレガントで保守しやすくなります。

シングルトンパターン

クラスのインスタンスが1つだけであることを保証し、同じオブジェクトをグローバルに共有します。フロントエンドで最もよくあるシナリオ:グローバル状態管理、モーダル管理、認証状態。

javascript
// 実装1:クロージャ
const Singleton = (function () {
  let instance;

  function createInstance(options) {
    return {
      name: options.name,
      log() {
        console.log(`インスタンス名: ${this.name}`);
      },
    };
  }

  return {
    getInstance(options) {
      if (!instance) {
        instance = createInstance(options);
      }
      return instance;
    },
  };
})();

const s1 = Singleton.getInstance({ name: "app" });
const s2 = Singleton.getInstance({ name: "other" });
console.log(s1 === s2); // true — 常に同じインスタンス
javascript
// 実装2:ES6クラス + スタティックメソッド
class ModalManager {
  static instance = null;

  constructor() {
    this.modals = [];
  }

  static getInstance() {
    if (!ModalManager.instance) {
      ModalManager.instance = new ModalManager();
    }
    return ModalManager.instance;
  }

  open(config) {
    const modal = { id: Date.now(), ...config, visible: true };
    this.modals.push(modal);
    return modal.id;
  }

  close(id) {
    const index = this.modals.findIndex((m) => m.id === id);
    if (index > -1) this.modals.splice(index, 1);
  }
}

オブザーバーパターン

javascript
class EventBus {
  constructor() {
    this.events = new Map();
  }

  on(event, handler) {
    if (!this.events.has(event)) this.events.set(event, new Set());
    this.events.get(event).add(handler);
    // 購読解除関数を返す
    return () => this.off(event, handler);
  }

  off(event, handler) {
    this.events.get(event)?.delete(handler);
  }

  emit(event, data) {
    this.events.get(event)?.forEach((handler) => handler(data));
  }
}

const bus = new EventBus();
const unsubscribe = bus.on("user:login", (user) =>
  console.log("ユーザーがログイン:", user),
);
bus.emit("user:login", { name: "アリス" });
unsubscribe(); // クリーンアップ

ストラテジーパターン

javascript
// ストラテジーなし:大きなswitch/if-else
function calculateDiscount(type, price) {
  if (type === "vip") return price * 0.8;
  if (type === "member") return price * 0.9;
  if (type === "newuser") return price - 20;
  return price;
}

// ストラテジーあり:拡張可能、開放閉鎖原則に従う
const discountStrategies = {
  vip: (price) => price * 0.8,
  member: (price) => price * 0.9,
  newuser: (price) => Math.max(0, price - 20),
  default: (price) => price,
};

function calculateDiscount(type, price) {
  const strategy = discountStrategies[type] || discountStrategies.default;
  return strategy(price);
}

// 新しい割引タイプの追加は既存コードへの変更ゼロ
discountStrategies.superVip = (price) => price * 0.7;

シングルトン・オブザーバー・ストラテジーの3つのパターンは、実際のフロントエンドシナリオの大部分をカバーします。マスターすることでよりクリーンで保守しやすいコードが書けます。

MIT Licensed