Observerパターンとは
Observerパターンは、あるオブジェクト(Subject)の状態が変わったとき、 そのオブジェクトに依存する複数のオブジェクト(Observer)に自動的に通知するパターンです。 GoFの行動パターン(Behavioral Pattern)に分類されます。
「オブジェクト間の1対多の依存関係を定義する。あるオブジェクトの状態が変化すると、 依存するすべてのオブジェクトに自動的に通知され、更新される。」— GoF
問題:密結合な通知処理
Observerパターンを使わない場合、通知処理がこのように密結合になります:
// アンチパターン: 通知先を直接呼び出している
public class StockPrice {
private double price;
private EmailAlert emailAlert = new EmailAlert();
private MobileApp mobileApp = new MobileApp();
private Dashboard dashboard = new Dashboard();
public void setPrice(double newPrice) {
this.price = newPrice;
// 通知先が増えるたびにここを変更しなければならない
emailAlert.notify(newPrice);
mobileApp.notify(newPrice);
dashboard.notify(newPrice);
}
}
通知先が1つ増えるたびに StockPrice を変更する必要があります。
開放閉鎖原則(OCP)に違反し、テストも困難です。
解決:Observerパターンの実装
Step 1: Observer インターフェースを定義
// Observer インターフェース
public interface StockObserver {
void update(String stockSymbol, double oldPrice, double newPrice);
}
Step 2: Subject インターフェースを定義
// Subject インターフェース
public interface StockSubject {
void addObserver(StockObserver observer);
void removeObserver(StockObserver observer);
void notifyObservers();
}
Step 3: Concrete Subject を実装
// Concrete Subject: 株価
public class StockMarket implements StockSubject {
private final List<StockObserver> observers = new ArrayList<>();
private final Map<String, Double> stockPrices = new HashMap<>();
@Override
public void addObserver(StockObserver observer) {
observers.add(observer);
}
@Override
public void removeObserver(StockObserver observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
// 実装は updatePrice() 内で行う
}
public void updatePrice(String symbol, double newPrice) {
double oldPrice = stockPrices.getOrDefault(symbol, 0.0);
stockPrices.put(symbol, newPrice);
// すべての Observer に通知
for (StockObserver observer : observers) {
observer.update(symbol, oldPrice, newPrice);
}
}
public double getPrice(String symbol) {
return stockPrices.getOrDefault(symbol, 0.0);
}
}
Step 4: Concrete Observer を実装
// Concrete Observer 1: メールアラート
public class EmailAlertObserver implements StockObserver {
private final String email;
private final double threshold; // 変動率の閾値 (%)
public EmailAlertObserver(String email, double threshold) {
this.email = email;
this.threshold = threshold;
}
@Override
public void update(String symbol, double oldPrice, double newPrice) {
if (oldPrice == 0) return;
double changeRate = Math.abs((newPrice - oldPrice) / oldPrice * 100);
if (changeRate >= threshold) {
System.out.printf("[EMAIL → %s] %s が %.1f%% 変動: %.2f → %.2f%n",
email, symbol, changeRate, oldPrice, newPrice);
}
}
}
// Concrete Observer 2: モバイルプッシュ通知
public class MobilePushObserver implements StockObserver {
private final String deviceId;
public MobilePushObserver(String deviceId) {
this.deviceId = deviceId;
}
@Override
public void update(String symbol, double oldPrice, double newPrice) {
String direction = newPrice > oldPrice ? "↑" : "↓";
System.out.printf("[PUSH → %s] %s %s %.2f円%n",
deviceId, symbol, direction, newPrice);
}
}
// Concrete Observer 3: ダッシュボード(リアルタイム表示)
public class DashboardObserver implements StockObserver {
private final Map<String, Double> displayedPrices = new HashMap<>();
@Override
public void update(String symbol, double oldPrice, double newPrice) {
displayedPrices.put(symbol, newPrice);
System.out.printf("[DASHBOARD] %s: %.2f円 (前回: %.2f円)%n",
symbol, newPrice, oldPrice);
}
public void printAll() {
System.out.println("=== 現在の株価一覧 ===");
displayedPrices.forEach((symbol, price) ->
System.out.printf(" %s: %.2f円%n", symbol, price));
}
}
Step 5: 実際の使用例
public class Main {
public static void main(String[] args) {
StockMarket market = new StockMarket();
// Observer を登録
EmailAlertObserver emailAlert =
new EmailAlertObserver("zhongtao@example.com", 5.0); // 5%以上変動でメール
MobilePushObserver mobilePush = new MobilePushObserver("iPhone-XYZ");
DashboardObserver dashboard = new DashboardObserver();
market.addObserver(emailAlert);
market.addObserver(mobilePush);
market.addObserver(dashboard);
// 株価を更新 → 全 Observer に自動通知
System.out.println("--- トヨタ株価更新 ---");
market.updatePrice("TOYOTA", 2850.00);
System.out.println("\n--- ソニー株価更新(大幅変動)---");
market.updatePrice("SONY", 12500.00);
market.updatePrice("SONY", 13200.00); // 5.6%上昇 → メールが飛ぶ
System.out.println("\n--- Observer の動的な削除 ---");
market.removeObserver(mobilePush); // モバイル通知を解除
market.updatePrice("TOYOTA", 2900.00); // mobilePush には通知されない
System.out.println();
dashboard.printAll();
}
}
出力結果:
--- トヨタ株価更新 ---
[PUSH → iPhone-XYZ] TOYOTA ↑ 2850.00円
[DASHBOARD] TOYOTA: 2850.00円 (前回: 0.00円)
--- ソニー株価更新(大幅変動)---
[PUSH → iPhone-XYZ] SONY ↑ 12500.00円
[DASHBOARD] SONY: 12500.00円 (前回: 0.00円)
[EMAIL → zhongtao@example.com] SONY が 5.6% 変動: 12500.00 → 13200.00
[PUSH → iPhone-XYZ] SONY ↑ 13200.00円
[DASHBOARD] SONY: 13200.00円 (前回: 12500.00円)
--- Observer の動的な削除 ---
[EMAIL → zhongtao@example.com] TOYOTA が ... (閾値未達のため省略)
[DASHBOARD] TOYOTA: 2900.00円 (前回: 2850.00円)
=== 現在の株価一覧 ===
TOYOTA: 2900.00円
SONY: 13200.00円
UML クラス図
┌───────────────┐ ┌──────────────────────┐
│ StockMarket │────────▶│ StockObserver │
│ (Subject) │ notifies│ <> │
├───────────────┤ ├──────────────────────┤
│ -observers │ │ +update(symbol, │
│ -stockPrices │ │ oldPrice, newPrice)│
├───────────────┤ └──────────┬───────────┘
│ +addObserver()│ │ implements
│ +removeObs.() │ ┌─────────┼──────────┐
│ +updatePrice()│ ▼ ▼ ▼
└───────────────┘ ┌───────────┐ ┌────────┐ ┌──────────┐
│ Email │ │Mobile │ │Dashboard │
│ Alert │ │Push │ │Observer │
└───────────┘ └────────┘ └──────────┘
Java 標準ライブラリとの関係
Java 標準ライブラリにも Observer パターンの実装が組み込まれています。
ただし、java.util.Observer と java.util.Observable は
Java 9 で非推奨(Deprecated)になりました。
現代の Java 開発では以下の代替を使用します。
| 用途 | 推奨クラス/フレームワーク |
|---|---|
| GUIイベント処理 | java.beans.PropertyChangeListener |
| リアクティブストリーム | java.util.concurrent.Flow(Java 9+) |
| Springのイベント | ApplicationEvent + @EventListener |
| Android | LiveData、StateFlow(Kotlin) |
Strategy パターンとの違い
前回解説した Strategy パターンと比較すると、 目的が明確に異なります。
| Strategy パターン | Observer パターン | |
|---|---|---|
| 目的 | アルゴリズムの交換 | 状態変化の通知 |
| 関係 | 1対1(Context と Strategy) | 1対多(Subject と複数Observer) |
| タイミング | 呼び出し時に選択 | 状態変化時に自動発火 |
まとめ
Observerパターンは、「状態変化を複数の場所に伝えたい」ときに最も力を発揮します。 Subject と Observer を疎結合に保つことで、新しい通知先を追加しても既存コードを一切変更せずに済みます。 現代のリアクティブプログラミング(RxJava、Kotlin Flow)も、この思想を発展させたものです。