1. オブザーバー(Observer)パターン入門
オブザーバー(Observer)パターンは、最もよく知られた基本的なデザインパターンの一つです。あるオブジェクト(観測対象、Subject)が、自身の変更を、その変更を購読している他のオブジェクト(オブザーバー、observers)に通知する状況を表します。
もっと簡単に言えば、Telegram のチャンネル(観測対象)があり、「購読者」(オブザーバー)がいるイメージです。新しい投稿が出るたびに、チャンネルは全ての購読者に通知し、購読者は読む・無視する・購読解除するなどを自分で決めます。
プログラミングでは、このパターンにより、オブジェクト同士を直接強く結び付けずに、イベントや状態変化を関心のあるオブジェクトへ自動的に通知できます。 これは、柔軟で拡張可能、かつ保守しやすいシステムを構築するうえで重要です。
オブザーバー・パターンはどこで使われる?
- GUI(Swing、AWT、JavaFX)— イベントリスナー。
- リアクティブライブラリ(RxJava、Project Reactor)。
- ビジネスロジック:モデル状態の変更に反応する。
- ゲームエンジン(衝突・勝利・敗北などのイベント)。
- 「何が起きたか」と「それにどう対処するか」を分離したい場面すべて。
Java のイベントとリスナーとの関係
実のところ、Java のイベントモデル全体が「オブザーバー」に基づいています。button.addActionListener(listener); と書くとき、まさにこのパターンを実装しています:
- 観測対象 — ボタン(または他のコンポーネント)。
- オブザーバー — actionPerformed() を実装するあなたのリスナー。
- イベント — ユーザーがクリック、マウスオーバーなどを行う。
- 通知 — コンポーネントが actionPerformed() を呼び出す。
これはまさに古典的な Observer の実装です!
2. オブザーバー・パターンの古典的実装
Swing や AWT を使わずに、自分のクラスでパターンを実装してみましょう。仕組みが分かれば魔法ではないと分かります。
パターンの主要要素
- Observable (Subject) — 観測対象。オブザーバーのリストを保持し、変更を通知する。
- Observer — オブザーバーのインターフェース。通常は update() メソッドを1つ持つ。
例: 温度計とエアコン
オブザーバーのインターフェース
public interface TemperatureObserver {
void temperatureChanged(int newTemperature);
}
クラス「Thermometer」(観測対象)
import java.util.*;
public class Thermometer {
private int temperature;
private final List<TemperatureObserver> observers = new ArrayList<>();
public void addObserver(TemperatureObserver observer) {
observers.add(observer);
}
public void removeObserver(TemperatureObserver observer) {
observers.remove(observer);
}
public void setTemperature(int newTemperature) {
if (this.temperature != newTemperature) {
this.temperature = newTemperature;
notifyObservers();
}
}
private void notifyObservers() {
for (TemperatureObserver observer : observers) {
observer.temperatureChanged(temperature);
}
}
}
オブザーバー例 — 「AirConditioner」
public class AirConditioner implements TemperatureObserver {
@Override
public void temperatureChanged(int newTemperature) {
if (newTemperature > 25) {
System.out.println("エアコンをオンにしました!暑い: " + newTemperature + "°C");
} else {
System.out.println("エアコンはオフ。温度: " + newTemperature + "°C");
}
}
}
使用例
public class Main {
public static void main(String[] args) {
Thermometer thermometer = new Thermometer();
AirConditioner conditioner = new AirConditioner();
thermometer.addObserver(conditioner);
thermometer.setTemperature(22); // エアコンはオフ。温度: 22°C
thermometer.setTemperature(28); // エアコンをオンにしました!暑い: 28°C
}
}
以上、これで十分! さらにオブザーバーをいくつ追加しても、温度が変われば全員に通知されます。
パターンの図解
flowchart LR
T["温度計 (Observable)"] -- 通知する --> AC["エアコン (Observer)"]
T -- 通知する --> L["ロガー (Observer)"]
T -- 通知する --> Alarm["警報 (Observer)"]
現代の事情: 非推奨の Observable と新しいアプローチ
標準ライブラリには java.util.Observable と java.util.Observer が存在しましたが、Java 9 で非推奨(deprecated)になりました。理由は柔軟性の不足です(たとえば、Observable はクラスであってインターフェースではないため、他のクラスから継承しにくい、など)。
現代的なやり方は、自前のリスナーインターフェースと購読/解除ロジックを設計すること(上の例のとおり)。その方が柔軟で安全で、実務の要件に適合します。
3. 例: 購読者付きのミニアプリ
値の変化を購読できる「クリックカウンター」を作ってみましょう。
リスナーのインターフェース
public interface CounterListener {
void counterChanged(int newValue);
}
カウンタークラス
import java.util.*;
public class Counter {
private int value = 0;
private final List<CounterListener> listeners = new ArrayList<>();
public void addCounterListener(CounterListener l) {
listeners.add(l);
}
public void removeCounterListener(CounterListener l) {
listeners.remove(l);
}
public void increment() {
value++;
notifyListeners();
}
private void notifyListeners() {
for (CounterListener l : listeners) {
l.counterChanged(value);
}
}
public int getValue() {
return value;
}
}
リスナー: メッセージを出力
public class ConsoleCounterListener implements CounterListener {
@Override
public void counterChanged(int newValue) {
System.out.println("カウンターが更新されました: " + newValue);
}
}
使用例
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
counter.addCounterListener(new ConsoleCounterListener());
counter.increment(); // カウンターが更新されました: 1
counter.increment(); // カウンターが更新されました: 2
}
}
4. 役に立つ注意点
現代的な代替と拡張
現場では、購読のために無名クラスやラムダ式を使うことが多いです: counter.addCounterListener(newValue -> System.out.println("新しい値: " + newValue));
(この書き方にするには、インターフェースが関数型、つまり抽象メソッドが1つだけである必要があります。)
また、リアクティブライブラリ(RxJava、Project Reactor)も人気で、「オブザーバー」をイベントストリーム、フィルタリング、非同期などと併せて実現できます。本質を理解するには、上で解説した古典的な構成で十分です。
オブザーバー・パターンの実用例
- データモデル。 モデル(タスク、商品、ユーザーなど)が変わると、ビューに更新を通知する。
- ロギング。 システム全体のイベントに反応するロガー購読者。
- 通知。 状態が変わったら、メール、プッシュ通知、Telegram へのメッセージを送る。
- ゲーム。 体力の変化、敵の出現、ステージの完了。
- マルチスレッド。 あるスレッドがイベントを発行し、他のスレッドが反応する。
5. オブザーバー・パターン実装のよくある落とし穴
エラー No.1: リスナーの削除を忘れる。 不要になったのに削除しないと、リスナーは通知を受け続けます。長寿命のアプリではメモリリークの原因になります。
エラー No.2: ハンドラ内での重い/ブロッキング処理。 ハンドラが重い処理(IO、DB など)を行うと、特に通知が UI スレッドから送られる場合にアプリが「固まる」ことがあります。重い処理はバックグラウンドスレッドへ移しましょう。
エラー No.3: リスナー内の例外。 1つのリスナーで例外が発生すると、他への配信が中断されることがあります。リスナー呼び出しは try-catch で囲み、エラーをロギングしましょう。
エラー No.4: 同じリスナーを複数回登録。 同じリスナーが複数回追加されると、イベントを重複回数だけ受け取ります。登録状況を管理し、重複追加を防ぎましょう。
エラー No.5: 観測対象とオブザーバーの強い結合。 観測対象が特定のオブザーバー実装を知ってしまうと、疎結合が崩れます。インターフェースのみに依存しましょう(例: TemperatureObserver, CounterListener)。
GO TO FULL VERSION