
Observer を Java で
Observer は、 振る舞いに関するデザインパターンの一つで、 オブジェクトが別のオブジェクトに状態の変化を通知できるようにします。
Observer パターンは、 サブスクライバー・インターフェースを実装するいかなるオブジェクトのイベント通知の申し込みと停止をする方法を提供します。
複雑度:
人気度:
使用例: Observer パターンは、 Java コードではよく見かけます (特に、 GUI コンポーネントで)。 他のオブジェクトで起きるイベントに反応することを、 そのクラスに結合せずに可能とします。
Java のコア・ライブラリーでのこのパターンの使用例です:
-
java.util.Observer
/java.util.Observable
(実際の使用は稀) -
java.util.EventListener
の全実装 (Swing コンポーネントいたるところ) -
javax.servlet.http.HttpSessionBindingListener
-
javax.servlet.http.HttpSessionAttributeListener
-
javax.faces.event.PhaseListener
見つけ方: このパターンは、 オブジェクトをリストに保存するサブスクリプション・メソッドと、 そのリスト中のオブジェクトの更新メソッドの呼び出しにより、 識別できます。
イベント・サブスクリプション
この例では、 Observer パターンを使い、 あるテキスト・エディターのオブジェクト間の間接的共同作業を確立します。 Editor
オブジェクトに変化があるたびに、 変化をサブスクライバーに通知します。 EmailNotificationListener
と LogOpenListener
は、 その主たる振る舞いを実行して、 通知に反応します。
サブスクライバー・クラスはエディターのクラスとは結合されておらず、 必要なら他のアプリでも使えます。 Editor
クラスは、 抽象サブスクライバー・インターフェースにのみ依存しています。 このため、 エディターのコードを変更することなく、 新しいサブスクライバーを追加できます。
publisher
publisher/EventManager.java: 基本的なパブリッシャー
package refactoring_guru.observer.example.publisher;
import refactoring_guru.observer.example.listeners.EventListener;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EventManager {
Map<String, List<EventListener>> listeners = new HashMap<>();
public EventManager(String... operations) {
for (String operation : operations) {
this.listeners.put(operation, new ArrayList<>());
}
}
public void subscribe(String eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.add(listener);
}
public void unsubscribe(String eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.remove(listener);
}
public void notify(String eventType, File file) {
List<EventListener> users = listeners.get(eventType);
for (EventListener listener : users) {
listener.update(eventType, file);
}
}
}
editor
editor/Editor.java: 他のオブジェクトから追跡される具象パブリッシャー
package refactoring_guru.observer.example.editor;
import refactoring_guru.observer.example.publisher.EventManager;
import java.io.File;
public class Editor {
public EventManager events;
private File file;
public Editor() {
this.events = new EventManager("open", "save");
}
public void openFile(String filePath) {
this.file = new File(filePath);
events.notify("open", file);
}
public void saveFile() throws Exception {
if (this.file != null) {
events.notify("save", file);
} else {
throw new Exception("Please open a file first.");
}
}
}
listeners
listeners/EventListener.java: 共通のオブザーバー・インターフェース
package refactoring_guru.observer.example.listeners;
import java.io.File;
public interface EventListener {
void update(String eventType, File file);
}
listeners/EmailNotificationListener.java: 通知受け付け後、 メールを送信
package refactoring_guru.observer.example.listeners;
import java.io.File;
public class EmailNotificationListener implements EventListener {
private String email;
public EmailNotificationListener(String email) {
this.email = email;
}
@Override
public void update(String eventType, File file) {
System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
}
}
listeners/LogOpenListener.java: 通知受け付け後、 ログにメッセージを書き込む
package refactoring_guru.observer.example.listeners;
import java.io.File;
public class LogOpenListener implements EventListener {
private File log;
public LogOpenListener(String fileName) {
this.log = new File(fileName);
}
@Override
public void update(String eventType, File file) {
System.out.println("Save to log " + log + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
}
}
Demo.java: 初期化コード
package refactoring_guru.observer.example;
import refactoring_guru.observer.example.editor.Editor;
import refactoring_guru.observer.example.listeners.EmailNotificationListener;
import refactoring_guru.observer.example.listeners.LogOpenListener;
public class Demo {
public static void main(String[] args) {
Editor editor = new Editor();
editor.events.subscribe("open", new LogOpenListener("/path/to/log/file.txt"));
editor.events.subscribe("save", new EmailNotificationListener("admin@example.com"));
try {
editor.openFile("test.txt");
editor.saveFile();
} catch (Exception e) {
e.printStackTrace();
}
}
}
OutputDemo.txt: 実行結果
Save to log \path\to\log\file.txt: Someone has performed open operation with the following file: test.txt
Email to admin@example.com: Someone has performed save operation with the following file: test.txt