概要
あるオブジェクトの状態が変化したとき、それに依存する複数のオブジェクトに自動的に通知する。Observer パターンは、GUI のイベントリスナー、メッセージキューのサブスクライバー、ドメインイベントの伝播など、1対多の通知が必要な場面で広く使われます。java.util.Observer は Java 9 で非推奨となりましたが、パターンの考え方は java.beans.PropertyChangeListener や各種フレームワークのイベント機構に引き継がれています。この記事では、イベント通知システムを題材に Publisher と Listener の構造を実装し、record によるイベントデータの定義、Java 21 の sealed interface でイベント種別を型安全に分類する方法を確認します。
使いどころ
ユーザー登録イベントを複数のリスナー(ログ記録・ウェルカムメール送信・監査ログ)に同時通知する
在庫数の変更を画面表示コンポーネントとアラート通知コンポーネントに自動的に反映する
設定ファイルの変更を検知し、関連する全モジュールにリロードイベントを通知する
コード例
import java.util.ArrayList;
import java.util.List;
public class ObserverPatternDemo {
record Event(String type, Object data) {}
interface EventListener {
void onEvent(Event event);
}
static class EventPublisher {
private final List<EventListener> listeners
= new ArrayList<>();
public void subscribe(EventListener listener) {
listeners.add(listener);
}
public void unsubscribe(EventListener listener) {
listeners.remove(listener);
}
public void publish(Event event) {
System.out.println("[Publisher] " + event);
for (var listener : listeners) {
listener.onEvent(event);
}
}
}
static class LogListener implements EventListener {
@Override public void onEvent(Event event) {
System.out.println("[Log] " + event.type()
+ ": " + event.data());
}
}
static class EmailListener implements EventListener {
@Override public void onEvent(Event event) {
if ("USER_REGISTERED".equals(event.type())) {
System.out.println("[Email] ウェルカムメール: "
+ event.data());
}
}
}
static class AuditListener implements EventListener {
private int count = 0;
@Override public void onEvent(Event event) {
count++;
System.out.println("[Audit] #" + count
+ ": " + event);
}
public int getCount() { return count; }
}
public static void main(String[] args) {
var publisher = new EventPublisher();
var log = new LogListener();
var email = new EmailListener();
var audit = new AuditListener();
publisher.subscribe(log);
publisher.subscribe(email);
publisher.subscribe(audit);
publisher.publish(new Event("USER_REGISTERED",
"user_001, 山田太郎"));
publisher.publish(new Event("ORDER_PLACED",
"ord_100, 5000円"));
publisher.unsubscribe(email);
publisher.publish(new Event("USER_REGISTERED",
"user_002, 鈴木花子"));
System.out.println("監査イベント数: "
+ audit.getCount());
}
}Version Coverage
record でイベントデータ(Event)を不変オブジェクトとして定義でき、アクセサメソッドで型安全にフィールド値を取得できる。
// Java 17: record でイベントデータを型安全に
record Event(String type, Object data) {}
interface EventListener {
void onEvent(Event event);
}
public void publish(Event event) {
for (var listener : listeners) {
listener.onEvent(event);
}
}Library Comparison
注意点
リスナーの登録を解除しないとメモリリークの原因になる。特に匿名クラスやラムダ式で登録した場合、解除用の参照を保持しておくこと
通知の順序はリスト登録順に依存するが、順序に依存するロジックを書くとリスナー追加時に予期しない挙動が起きる。通知順序に依存しない設計にする
リスナー内で例外が発生すると後続のリスナーに通知が届かない。try-catch で個別に保護するか、例外を収集して最後にまとめるかは設計で決める
java.util.Observer / Observable は Java 9 以降で非推奨。新規コードでは自前の interface か java.beans.PropertyChangeListener を使う
FAQ
Observable がクラスで interface ではないため多重継承できず、通知メソッドの引数が Object で型安全性が低かったためです。PropertyChangeListener の利用が推奨されています。
ExecutorService にリスナーの呼び出しを submit する方法が一般的です。ただし、通知順序の保証や例外処理の設計が必要になります。
Observer は Publisher が直接リスナーを保持します。Pub/Sub はメッセージブローカーが仲介し、Publisher と Subscriber が互いを知りません。疎結合の度合いが異なります。