はじめに
NTT西日本の中川です。 本記事ではデザインパターンの一つである 「Observer(オブザーバー)パターン」 をJavaScriptを利用してご紹介します。本記事は、2026年2月時点の情報に基づきます。
対象読者
本記事が想定する対象読者は次の通りです。
- フロントエンドエンジニア
- リアルタイム反映に興味があり、仕組みを理解したい人
- 手を動かしながら理解を進めたい人
背景
プログラミングを進めていくと、「コードの複雑化(スパゲッティコード)」 が起こってしまうことも多いと思います。 「ボタンを押したら、ヘッダーの通知アイコンを変えて、サイドバーの数字も更新して、ログも表示して……」といったように、一つのアクションに対してあちこちを更新しようとすると、コードが複雑に絡み合い、どこを直せばいいか分からなくなることがあります。
今回は、そんな課題を整理するのに役立つデザインパターン 「Observer(オブザーバー)パターン」 を、ツール不要、コピー&ペーストだけで今すぐ試せるプログラムと共に紹介します。ぜひ体験してみてください。
1. Observerパターンは「YouTubeのチャンネル登録」をするイメージ
Observerパターンを一言で言うと、「状態が変わった時に、登録者に一斉にお知らせする仕組み」 です。
例えば...
- Subject(発信者): YouTubeチャンネル。「動画を出したよ!」と知らせる役割。
- Observer(受信者): 視聴者。「動画が出たら教えてね」と登録している役割。
チャンネル側は、誰が登録しているかは詳しく知りません。 ただ「リストに載っている人全員に通知を送る」だけ。これによって、お互いのコードが過度に干渉しない状態を作れます。これを 「疎結合(そけつごう)」 と呼び、修正に強いコードの基本とされています。[1]
[1] ※ 「疎結合(そけつごう)」 とは、各システムやモジュール間での依存関係(AがないとBが動かないなど)が弱く、変更や差し替えが容易な設計のことです。
2. コンソールで今すぐ試す!
まずは、動くものを見てみる方が理解しやすいので、ブラウザの「検証ツール(コンソール)」に貼り付けるだけで動く、シンプルなコードを用意しました。
手順:
- このブラウザで F12キー(Macは Cmd + Option + I)を押してコンソールを開く。
- 以下のコードをコピーして貼り付け、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 で柔軟に制御可能 |
まとめ
- 「知らせる側」と「やる側」を分ける: 役割を分離することで、コードの整理がしやすくなります。
- 登録と解除の管理: 実務開発では、メモリ管理の観点から
unsubscribeも意識しておくと安心です。 - 標準機能への理解:
addEventListenerもこのパターンの考え方を応用したものです。
今回は仕組みを深く理解するために、JavaScriptのクラスを用いて独自のObserverシステムを構築しました。 最初は難しく感じるかもしれませんが、YouTubeのチャンネル登録など身近なサービスをイメージしながら取り入れてみてください。このパターンを意識することで、普段のコードをより拡張しやすく、メンテナンス性の高いものへと改善できるはずです。 さらに高度な監視を行いたい場合は、参考文献に挙げたブラウザ標準のAPIもぜひ活用してみてください。
執筆者
中川 拓哉(NTT西日本 デジタル革新本部 デジタル改革推進部所属)
NTT西日本の法人向け顧客ポータルサイトの開発・運営に従事。
好きな技術スタック:TypeScript, Vue.js, GraphQL, Laravel
参考文献
商標
- YouTube は Google LLC の商標または登録商標です。
- その他、本記事に記載されている会社名、製品名、サービス名等は、各社の商標または登録商標です。