Observerパターンメモ
「Java本格入門」の中でデザインパターンについて触れられており、理解を深めるために実際にコーディングしながら読み進めたので、忘れないようにメモ。
認識に間違いがあれば指摘していただけると幸いです。
Observer パターンとは
Observer パターン(オブザーバ・パターン)とは、プログラム内のオブジェクトの状態を観察(英: observe)するようなプログラムで使われるデザインパターンの一種。出版-購読型モデルとも呼ばれる。暗黙的呼び出しの原則と関係が深い。 分散イベント処理システムの実装に主に使われる。言語によっては、このパターンで扱われる問題は言語が持つイベント処理構文で処理される。リアルタイムのアプリケーション配置の手段として興味深い機能である。
Wikipediaにも記載されているように、ObserverパターンはPublish(発行)-Subscribe(購読)パターンとも呼ばれるそうです。
個人的には、Publish-Subscribeパターンの方がしっくりきました。
Observerパターンで登場するクラス
Observerパターンで登場するのは、以下の2クラスがメイン。
- Subject(or Publish)
- Observerに対して通知を行うクラス。
- 通知先となるObserverの登録(
setObserver
)・削除(deleteObserver
)機能を提供する。
- Observer(or Subscribe)
- Subjectからの通知を受け、処理を行うクラス。
- 通知を受ける
update
メソッドを提供する。
こんなシナリオを考えてみる
- ブロガーがブログに記事を投稿する。
- 記事が投稿されたら、購読者へ投稿通知が届く。
作成するクラスは以下のようになります。
- Blogクラス - ブログ
- Subjectにあたる。
- Subscriberクラス - ブログの購読者
- Observerにあたる。
コード
Subjectインターフェース
/** * <h1>Subjectインターフェース</h1> * * Observerの登録・削除・通知機能を提供します。<br/> * * @param <T> <code>getSubjectStatus</code>メソッドの戻り値の型 */ public interface Subject<T> { /** * Observerを設定します。 * * @param observe Observer */ void setObserver(final Observer observe); /** * Observerを一括削除します。 */ void deleteObservers(); /** * Observerを削除します。<br/> * このメソッドは、Observer側で呼び出されることを想定しています。 * * @param observer 削除対象のObserver */ void deleteObserver(Observer observer); /** * Observerに通知を行います。 */ void notifyObservers(); /** * Subjectのステータスを取得します。 * * @return ステータス */ T getSubjectStatus(); /** * 処理を実行します。<br/> * 登録されたObserverへ通知は、このメソッド内の処理で行われます。 */ void execute(); }
Observerインターフェース
** * 観察者(通知を受ける側) */ public interface Observer { /** * Subjectからの通知を受け、処理を行います。<br/> * <code>update</code>の呼び出しは、Subjectによって行われます。 * * @param subject Subject */ void update(Subject subject); }
Blog(ブログ)クラス
import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * <h1>ブログクラス</h1> * * 記事の投稿を行い、購読者へ通知を行います。 * */ public class Blog implements Subject<String> { /** 通知先リスト */ private final List<Observer> observers = new ArrayList<>(); /** 最新記事のタイトル */ private String latestTitle = ""; @Override public void setObserver(final Observer observe) { this.observers.add(observe); } @Override public void deleteObservers() { this.observers.clear(); } @Override public void deleteObserver(final Observer observer) { Objects.requireNonNull(observer, "削除対象のObserverがnullです。"); this.observers.remove(observer); } @Override public void notifyObservers() { final int LOOP_SIZE = observers.size(); for (int i = 0 ; i < LOOP_SIZE ; i++) { observers.get(i).update(this); } } /** * 最新記事のタイトルを取得します。 * * @return 最新記事のタイトル。 */ @Override public String getSubjectStatus() { return this.latestTitle; } /** * 記事を投稿します。<br/> * 購読者(Observer)へ通知が行われます。 */ @Override public void execute() { System.out.println("記事を投稿しました。"); this.notifyObservers(); } /** * 記事のタイトルを設定します。 * * @param title タイトル */ public void setLatestTitle(final String title){ this.latestTitle = title; } /** * * 購読者数を取得します。 * * @return 購読者数 */ public int getSubscribeSize(){ return this.observers.size(); } }
Subscriber(購読者)クラス
Subscriberクラスではあらかじめ購読回数を設定しておき、 上限に達した場合は通知が来ないように自身のインスタンスをSubjectから除外します。
import java.util.Objects; /** * <h1>購読者クラス</h1> */ public class Subscriber implements Observer { /** ユーザ名 */ private final String userName; /** 通知上限回数 */ private final int notifyMaxCount; /** 通知回数 */ private int notifyCount = 0; /** * コンストラクタ。<br/> * * ユーザ名が null の場合、<code>NullPointerException</code>がスローされます。 * * @param userName ユーザ名 * @param notifyMaxCount 通知上限回数 */ public Subscriber(final String userName, final int notifyMaxCount){ Objects.requireNonNull(userName); this.userName = userName; this.notifyMaxCount = notifyMaxCount; } /** * 記事の投稿通知を受け取ります。<br/> * なお、通知回数が上限値に達した場合には、通知対象から自身を削除します。 * * @param subject 購読先のBlogインスタンス */ @Override public void update(final Subject subject) { System.out.printf("%sに「%s」の投稿通知が届きました。%n", this.userName, subject.getSubjectStatus()); // 通知上限に達した場合には、通知対象から除外する。 if(++notifyCount == notifyMaxCount){ subject.deleteObserver(this); } } /** * ユーザ名を取得します。 * * @return ユーザ名 */ public String getUserName(){ return this.userName; } }
シュミレート
public class Blogger { public static void main(String...args){ // Subjectの生成 Blog blog = new Blog(); // Observerの生成 Subscriber subscriber1 = new Subscriber("うさぎ", 2); Subscriber subscriber2 = new Subscriber("かめ", 1); // ObserverをSubjectへ登録 blog.setObserver(subscriber1); blog.setObserver(subscriber2); // ブログへ記事を投稿 blog.setLatestTitle("デザインパターンメモ"); blog.execute(); // ブログへ記事を投稿 blog.setLatestTitle("SpringBootメモ"); blog.execute(); } }
実行結果は以下の通りになります。
記事を投稿しました。 うさぎに「デザインパターンメモ」の投稿通知が届きました。 かめに「デザインパターンメモ」の投稿通知が届きました。 記事を投稿しました。 うさぎに「SpringBootメモ」の投稿通知が届きました。
まとめ
- Observerパターンは、状態監視に関するデザインパターン。
- SubjectがObserverへ通知を行う。
疑問
- Publish-Subscribeパターン
- Reactive Streamsって、Publish-Subscribeパターン?
- Observerの削除について
- 今回の例で、Observerの削除はObserver自身が行うようにしましたが、本来誰が削除するのが正しいのか…。