【リアルタイム反映の実現】ブラウザで完結!Observerパターンで「リアルタイムに連動する」アプリを作ろう

はじめに

NTT西日本の中川です。 本記事ではデザインパターンの一つである 「Observer(オブザーバー)パターン」 をJavaScriptを利用してご紹介します。本記事は、2026年2月時点の情報に基づきます。

対象読者

本記事が想定する対象読者は次の通りです。

  • フロントエンドエンジニア
  • リアルタイム反映に興味があり、仕組みを理解したい人
  • 手を動かしながら理解を進めたい人

背景

プログラミングを進めていくと、「コードの複雑化(スパゲッティコード)」 が起こってしまうことも多いと思います。 「ボタンを押したら、ヘッダーの通知アイコンを変えて、サイドバーの数字も更新して、ログも表示して……」といったように、一つのアクションに対してあちこちを更新しようとすると、コードが複雑に絡み合い、どこを直せばいいか分からなくなることがあります。

今回は、そんな課題を整理するのに役立つデザインパターン 「Observer(オブザーバー)パターン」 を、ツール不要、コピー&ペーストだけで今すぐ試せるプログラムと共に紹介します。ぜひ体験してみてください。


1. Observerパターンは「YouTubeのチャンネル登録」をするイメージ

Observerパターンを一言で言うと、「状態が変わった時に、登録者に一斉にお知らせする仕組み」 です。

例えば...

  • Subject(発信者): YouTubeチャンネル。「動画を出したよ!」と知らせる役割。
  • Observer(受信者): 視聴者。「動画が出たら教えてね」と登録している役割。

チャンネル側は、誰が登録しているかは詳しく知りません。 ただ「リストに載っている人全員に通知を送る」だけ。これによって、お互いのコードが過度に干渉しない状態を作れます。これを 「疎結合(そけつごう)」 と呼び、修正に強いコードの基本とされています。[1]

[1] ※ 「疎結合(そけつごう)」 とは、各システムやモジュール間での依存関係(AがないとBが動かないなど)が弱く、変更や差し替えが容易な設計のことです。


2. コンソールで今すぐ試す!

まずは、動くものを見てみる方が理解しやすいので、ブラウザの「検証ツール(コンソール)」に貼り付けるだけで動く、シンプルなコードを用意しました。

手順:

  1. このブラウザで F12キー(Macは Cmd + Option + I)を押してコンソールを開く。
  2. 以下のコードをコピーして貼り付け、Enter を押す。
// --- 1. 司令塔(チャンネル)の仕組み ---
class Subject {
  constructor() {
    this.observers = new Set(); // 登録者リスト(重複を防ぐためSetを使用)
  }
  // 登録(購読)
  subscribe(fn) { 
    this.observers.add(fn); 
    console.log("新しい視聴者が登録されました。");
  } 
  // 解除(購読解除)
  unsubscribe(fn) { 
    this.observers.delete(fn); 
    console.log("登録が解除されました。");
  }
  // 一斉通知
  notify(data) { 
    this.observers.forEach(fn => fn(data)); 
  } 
}

// --- 2. 実際に動かしてみる ---
const youtubeChannel = new Subject();

// 通知が来た時の処理を定義
const viewerA = (title) => console.log(`視聴者A:「${title}」の通知が来た!`);
const viewerB = (title) => console.log(`視聴者B:「${title}」のリアクション!`);

// AとBが登録
youtubeChannel.subscribe(viewerA);
youtubeChannel.subscribe(viewerB);

// 動画を投稿!(通知を実行)
youtubeChannel.notify("JavaScript入門");

// 視聴者Aが解除
youtubeChannel.unsubscribe(viewerA);

// 次の動画投稿(Bだけに通知が届く)
youtubeChannel.notify("デザインパターンの活用");

結果: 2回目の通知では、解除した視聴者Aにはメッセージが届かなくなります。このように「必要なときだけ通知を受け取る」制御が柔軟に行えます。


3. コードを読み解いてみよう

3-1. 司令塔(Subjectクラス)の役割

まず、通知を管理する「システム本体」を作ります。

class Subject {
  constructor() {
    this.observers = new Set(); // 登録者リスト
  }
  • class Subject: 通知システムの設計図となる部分です。
  • this.observers = new Set(): 重複登録を防ぐため、配列の代わりに Set オブジェクトを使用しています。ここが 「登録者名簿」 の役割を果たします。

3-2. 登録・解除・通知のメソッド

次に、名簿を操作する機能を作ります。

  subscribe(fn) { this.observers.add(fn); }
  unsubscribe(fn) { this.observers.delete(fn); }
  • subscribe / unsubscribe: 名簿に処理を追加したり、削除したりします。
  • 解除の重要性: 不要になった通知設定をそのままにすると、メモリの無駄遣いや予期せぬエラーの原因(メモリリーク)になる可能性があるため、解除機能は実務上の重要なポイントです。
  notify(data) { 
    this.observers.forEach(fn => fn(data)); 
  }
}
  • notify(data): 名簿に載っているすべての処理をループで実行します。

3-3. インスタンス化と実行

設計図から実体を作り、処理を「予約」します。

const youtubeChannel = new Subject();
youtubeChannel.subscribe((data) => { ... });
  • アロー関数: (data) => { ... } を渡すことで、「通知が来た時に実行してほしい内容」をあらかじめ登録しておきます。

4. 簡単なリアルタイム文字カウントWeb アプリ(解除機能付き)を作ってみよう!

「入力した文字数を複数の場所で表示する」アプリに、特定の通知をストップする機能を持たせてみましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Observerパターン実践</title>
    <style>
        body { font-family: sans-serif; padding: 20px; background: #f0f2f5; }
        .container { background: white; padding: 30px; border-radius: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); max-width: 700px; margin: auto; }
        .display-area { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-top: 20px; }
        .box { padding: 15px; border: 1px solid #eee; border-radius: 8px; text-align: center; }
        input { width: 100%; padding: 12px; font-size: 18px; border: 2px solid #ddd; border-radius: 8px; box-sizing: border-box; }
        button { margin-top: 10px; cursor: pointer; padding: 5px 10px; }
        .warning { color: red; font-weight: bold; }
    </style>
</head>
<body>

<div class="container">
    <h2>Observerパターン・文字数カウンター</h2>
    <input type="text" id="myInput" placeholder="入力して連動を確認...">

    <div class="display-area">
        <div class="box">
            <h4>文字数</h4>
            <span id="charCount">0</span>
        </div>
        <div class="box">
            <h4>制限チェック</h4>
            <div id="alertMsg">OK</div>
            <button id="stopAlert">連動を止める</button>
        </div>
        <div class="box">
            <h4>逆さま表示</h4>
            <div id="reverseText">-</div>
        </div>
    </div>
</div>

<script>
class InputSubject {
    constructor() { this.observers = new Set(); }
    subscribe(fn) { this.observers.add(fn); }
    unsubscribe(fn) { this.observers.delete(fn); }
    notify(text) { this.observers.forEach(fn => fn(text)); }
}

const inputSubject = new InputSubject();

// 通知が来た時の処理
const updateCount = (text) => {
    document.getElementById('charCount').innerText = text.length;
};
const updateAlert = (text) => {
    const msg = document.getElementById('alertMsg');
    if (text.length > 10) {
        msg.innerText = "10文字超過!";
        msg.className = "warning";
    } else {
        msg.innerText = "OK";
        msg.className = "";
    }
};
const updateReverse = (text) => {
    document.getElementById('reverseText').innerText = text.split('').reverse().join('');
};

// 登録
inputSubject.subscribe(updateCount);
inputSubject.subscribe(updateAlert);
inputSubject.subscribe(updateReverse);

// イベント監視
document.getElementById('myInput').addEventListener('input', (e) => {
    inputSubject.notify(e.target.value);
});

// 解除ボタン
document.getElementById('stopAlert').addEventListener('click', () => {
    inputSubject.unsubscribe(updateAlert);
    alert("制限チェックの連動を停止しました。");
});
</script>
</body>
</html>

5. なぜこの書き方が「保守性の高いコード」に繋がるのか?

特徴 命令的な書き方(密結合) Observerパターン(疎結合)
拡張性 既存の関数を毎回書き換える必要がある 新しい処理を subscribe するだけで完了
影響範囲 1つの修正が全体に波及しやすい 各処理が独立しているため影響が限定的
リソース管理 処理の解除が難しくなりがち unsubscribe で柔軟に制御可能

まとめ

  1. 「知らせる側」と「やる側」を分ける: 役割を分離することで、コードの整理がしやすくなります。
  2. 登録と解除の管理: 実務開発では、メモリ管理の観点から unsubscribe も意識しておくと安心です。
  3. 標準機能への理解: addEventListener もこのパターンの考え方を応用したものです。

今回は仕組みを深く理解するために、JavaScriptのクラスを用いて独自のObserverシステムを構築しました。 最初は難しく感じるかもしれませんが、YouTubeのチャンネル登録など身近なサービスをイメージしながら取り入れてみてください。このパターンを意識することで、普段のコードをより拡張しやすく、メンテナンス性の高いものへと改善できるはずです。 さらに高度な監視を行いたい場合は、参考文献に挙げたブラウザ標準のAPIもぜひ活用してみてください。

執筆者

中川 拓哉(NTT西日本 デジタル革新本部 デジタル改革推進部所属)
NTT西日本の法人向け顧客ポータルサイトの開発・運営に従事。
好きな技術スタック:TypeScript, Vue.js, GraphQL, Laravel

参考文献

商標

  • YouTube は Google LLC の商標または登録商標です。
  • その他、本記事に記載されている会社名、製品名、サービス名等は、各社の商標または登録商標です。

© NTT WEST, Inc.