Observerパターン|イベント駆動アーキテクチャの基礎を学ぶ
0:00
0:00
0:00
0:00
Observerパターン|イベント駆動アーキテクチャの基礎を学ぶ
更新日:2025年10月17日
アプリケーション開発において、「あるオブジェクトの状態が変化したら、関連する複数のオブジェクトに通知したい」という場面は頻繁に訪れます。Observerパターンは、この問題を疎結合な方法で解決する強力な設計パターンです。GUIプログラミングやMVCアーキテクチャの基礎となるこのパターンについて、実践的な実装例と共に考察してみました。Javaでのオブジェクト指向設計に興味をお持ちの方に参考になれば幸いです。
Observerパターンが解決する問題
具体的なシナリオ:ニュース配信システム
あるニュース配信会社が、最新ニュースを複数のチャンネル(テレビ、ラジオ、Webサイト)に配信するシステムを開発しているとします。要件は以下の通りです:
- ニュースが更新されたら、すべての登録チャンネルに自動通知
- チャンネルは動的に追加・削除可能
- 各チャンネルは独自の方法でニュースを表示
悪い設計例:密結合
❌ 問題のあるコード
class NewsAgency {
private String latestNews;
private TVChannel tvChannel;
private RadioChannel radioChannel;
private WebChannel webChannel;
public void setNews(String news) {
this.latestNews = news;
// すべてのチャンネルに直接通知(密結合)
if (tvChannel != null) {
tvChannel.displayNews(news);
}
if (radioChannel != null) {
radioChannel.announceNews(news);
}
if (webChannel != null) {
webChannel.publishNews(news);
}
}
}
この設計の問題点
・新しいチャンネルを追加するたびにNewsAgencyのコードを変更する必要がある
・NewsAgencyが各チャンネルの具体的なクラスに依存している(密結合)
・チャンネルの動的な追加・削除が困難
・テストが難しい(モック作成が困難)
・新しいチャンネルを追加するたびにNewsAgencyのコードを変更する必要がある
・NewsAgencyが各チャンネルの具体的なクラスに依存している(密結合)
・チャンネルの動的な追加・削除が困難
・テストが難しい(モック作成が困難)
Observerパターンによる解決
Observerパターンは、被観察者(Subject)と観察者(Observer)の関係を抽象化することで、疎結合な設計を実現します。被観察者は観察者のリストを保持し、状態変化時にすべての観察者に通知するだけです。どのような観察者が登録されているかを気にする必要はありません。
パターンの構造
登場人物
- Subject(被観察者):状態を持ち、Observerの登録・削除・通知を行う
- Observer(観察者):通知を受け取るためのインターフェース
- ConcreteSubject:具体的な被観察者(NewsAgency)
- ConcreteObserver:具体的な観察者(各チャンネル)
クラス図
シーケンス図
Java実装例
1. Observer インターフェース
/**
* 観察者インターフェース
* すべての具体的な観察者はこのインターフェースを実装する
*/
public interface NewsObserver {
/**
* ニュース更新時に呼び出されるメソッド
* @param news 最新のニュース
*/
void update(String news);
}
2. Subject インターフェース
/**
* 被観察者インターフェース
* 観察者の登録・削除・通知を行う
*/
public interface NewsPublisher {
/**
* 観察者を登録
* @param observer 登録する観察者
*/
void attach(NewsObserver observer);
/**
* 観察者を削除
* @param observer 削除する観察者
*/
void detach(NewsObserver observer);
/**
* すべての観察者に通知
*/
void notifyObservers();
}
3. ConcreteSubject(ニュース配信会社)
import java.util.ArrayList;
import java.util.List;
/**
* 具体的な被観察者:ニュース配信会社
*/
public class NewsAgency implements NewsPublisher {
// 観察者のリスト
private final List observers = new ArrayList<>();
// 最新ニュース
private String latestNews;
/**
* ニュースを設定し、観察者に通知
* @param news 新しいニュース
*/
public void setNews(String news) {
this.latestNews = news;
System.out.println("\n[NewsAgency] 新しいニュース: " + news);
notifyObservers();
}
@Override
public void attach(NewsObserver observer) {
observers.add(observer);
System.out.println("[NewsAgency] 観察者を登録しました");
}
@Override
public void detach(NewsObserver observer) {
observers.remove(observer);
System.out.println("[NewsAgency] 観察者を削除しました");
}
@Override
public void notifyObservers() {
System.out.println("[NewsAgency] " + observers.size() + "個のチャンネルに通知中...");
for (NewsObserver observer : observers) {
observer.update(latestNews);
}
}
}
4. ConcreteObserver(各チャンネル)
/**
* 具体的な観察者:ニュースチャンネル
*/
public class NewsChannel implements NewsObserver {
private final String channelName;
public NewsChannel(String channelName) {
this.channelName = channelName;
}
@Override
public void update(String news) {
System.out.println(" [" + channelName + "] ニュース受信: " + news);
// 各チャンネル独自の処理
displayNews(news);
}
private void displayNews(String news) {
// チャンネルごとの表示処理
// TVなら映像、ラジオなら音声、Webなら記事として表示
}
}
5. 使用例
public class ObserverPatternDemo {
public static void main(String[] args) {
// 被観察者を作成
NewsAgency agency = new NewsAgency();
// 観察者を作成
NewsObserver tvChannel = new NewsChannel("TVニュース24");
NewsObserver radioChannel = new NewsChannel("ラジオニュース");
NewsObserver webChannel = new NewsChannel("Webニュース");
// 観察者を登録
agency.attach(tvChannel);
agency.attach(radioChannel);
agency.attach(webChannel);
// ニュース配信(すべてのチャンネルに自動通知)
agency.setNews("大雨警報が発令されました");
// チャンネルの動的な削除
agency.detach(radioChannel);
// 次のニュース配信(2つのチャンネルのみに通知)
agency.setNews("新しい技術が発表されました");
}
}
実行結果
[NewsAgency] 観察者を登録しました
[NewsAgency] 観察者を登録しました
[NewsAgency] 観察者を登録しました
[NewsAgency] 新しいニュース: 大雨警報が発令されました
[NewsAgency] 3個のチャンネルに通知中...
[TVニュース24] ニュース受信: 大雨警報が発令されました
[ラジオニュース] ニュース受信: 大雨警報が発令されました
[Webニュース] ニュース受信: 大雨警報が発令されました
[NewsAgency] 観察者を削除しました
[NewsAgency] 新しいニュース: 新しい技術が発表されました
[NewsAgency] 2個のチャンネルに通知中...
[TVニュース24] ニュース受信: 新しい技術が発表されました
[Webニュース] ニュース受信: 新しい技術が発表されました
使用シーンとメリット・デメリット
実際の使用シーン
Observerパターンが活躍する場面
- GUIイベント処理:ボタンクリック、マウス移動などのイベントリスナー
- MVCアーキテクチャ:ModelとViewの疎結合な関連付け
- リアクティブプログラミング:データストリームの購読
- 株価通知システム:価格変動時の自動通知
- チャットアプリケーション:メッセージ送信時の全員通知
- センサーシステム:温度・湿度変化の監視
メリット
| メリット | 説明 |
|---|---|
| 疎結合 | SubjectとObserverが独立し、相互の変更が影響しにくい |
| 拡張性 | 新しいObserverの追加が容易で、既存コードの変更不要 |
| 動的な関係 | 実行時に観察者を追加・削除可能 |
| 再利用性 | SubjectとObserverを別々に再利用できる |
| Open/Closed原則 | 拡張に開いて、修正に閉じている設計を実現 |
デメリットと注意点
| デメリット | 対策 |
|---|---|
| 通知順序の不確定性 | 順序が重要な場合は、優先度付きリストを使用 |
| メモリリークのリスク | 必ずdetach()を呼び出す。WeakReferenceの使用を検討 |
| 予期しない更新 | 通知のタイミングを明確に文書化 |
| デバッグの困難さ | ログ出力やデバッガのブレークポイントを活用 |
| パフォーマンス | 観察者が多い場合、非同期通知を検討 |
メモリリーク対策の重要性
特にGUIアプリケーションでは、ウィンドウを閉じてもObserverが登録されたままになり、メモリリークが発生することがあります。必ず適切なタイミングでdetach()を呼び出すか、WeakReferenceを使用してください。
特にGUIアプリケーションでは、ウィンドウを閉じてもObserverが登録されたままになり、メモリリークが発生することがあります。必ず適切なタイミングでdetach()を呼び出すか、WeakReferenceを使用してください。
他のパターンとの関連
Mediatorパターンとの違い
| 項目 | Observerパターン | Mediatorパターン |
|---|---|---|
| 目的 | 1対多の依存関係を定義 | 多対多の複雑な相互作用を整理 |
| 通信方向 | SubjectからObserverへの一方向 | Mediatorを介した双方向 |
| 関係性 | 分散型(各Observerが独立) | 集中型(Mediatorが制御) |
MVCアーキテクチャでの活用
Model-View-Controllerアーキテクチャにおいて、Observerパターンは中心的な役割を果たします:
- Model:Subject(被観察者)として動作
- View:Observer(観察者)として動作
- 関係:Modelの状態が変化すると、自動的にすべてのViewが更新される
この設計により、複数のViewが同じModelを共有でき、一つのModelに対して異なる表示方法を提供できます。例えば、同じデータをテーブル表示とグラフ表示の両方で同時に表現することが可能です。
Java標準ライブラリでの実装
java.util.Observer / Observable(非推奨)
Java 9以降で非推奨となりました。理由は以下の通りです:
Java 9以降で非推奨となりました。理由は以下の通りです:
- スレッドセーフではない
- Observableがクラスであり、継承が必要
- 通知メカニズムが限定的
現代的な実装方法
推奨される代替手段
- PropertyChangeListener:JavaBeansの標準機能
- EventListener:Swing/JavaFXのイベントシステム
- RxJava:リアクティブプログラミングライブラリ
- Spring Framework:ApplicationEventPublisher
PropertyChangeListenerの例
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
public class ModernNewsAgency {
private final PropertyChangeSupport support;
private String latestNews;
public ModernNewsAgency() {
support = new PropertyChangeSupport(this);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}
public void setNews(String news) {
String oldNews = this.latestNews;
this.latestNews = news;
support.firePropertyChange("news", oldNews, news);
}
}
参考・免責事項
本記事は2025年10月17日時点の情報に基づいて作成されています。記事内容は個人的な考察に基づくものであり、デザインパターンの理解を深めることを目的としています。実際のプロジェクトへの適用については、チームの設計方針や要件に応じて判断してください。コード例はJava 8以降を想定しています。
本記事は2025年10月17日時点の情報に基づいて作成されています。記事内容は個人的な考察に基づくものであり、デザインパターンの理解を深めることを目的としています。実際のプロジェクトへの適用については、チームの設計方針や要件に応じて判断してください。コード例はJava 8以降を想定しています。




コメント (0)
まだコメントはありません。