Edited at

デザインパターン「Observer」

More than 1 year has passed since last update.


はじめに

本投稿はJava言語で学ぶデザインパターン入門のデザインパターンをまとめた記事です。今回はObserverパターンになります。

まとめ一覧はこちら


Observerパターン


Observerパターンとは

観察される側(=Subject)と観察する側(=Observer)の2つの役割が存在し、Subjectの状態が変化した際にObserverに通知されるデザインパターンです。そのため、状態変化に応じた処理を記述する時に有効です。

どちらかというと観察よりも通知に重点が置かれており、Observerが能動的に通知を待っているのでPublish-Subscribeパターンと呼ばれることがあります。Publish(=発行)とSubscribe(=購読)という意味でこちらの方がイメージつきやすいかもしれませんね。

MVC(Model / View / Controller)アーキテクチャでいう。ModelViewの関係性に適応される場合が多いです。


Observerパターンのクラス図



  • SubjectObserverの2役のみ

  • 状態が変化した際にObserverに通知を行う。


Observerパターンのサンプル


抽象クラス


具象クラス

今回のサンンプルは、数を生成するSubjectObserverが観察し、生成したタイミングでObserverがその値を表示するという簡易的なものです。


Mainクラス


Main.java

public static void main(String[] args) {

NumberGenerator generator = new RandomNumberGenerator();
Observer degitObserver = new DegitObserver();
Observer graphObserver = new GraphObserver();
generator.addObserver(degitObserver);
generator.addObserver(graphObserver);
generator.execute();
}

ランダムな数を生成するRandomNumberGeneratordegitObservergraphObserverが観察し、数字が変化したタイミングでdegitObserverは値を、graphObserverが値を簡易グラフで出力します。


Observer


Observer


Observer.java

public interface Observer {

public abstract void update(NumberGenerator generator);
}

Observerのインターフェイスクラスです。実際観察するクラスはこのインターフェイスを実装します。数が生成されたタイミングで処理をするための、updateメソッドのみになります。


Subject


NumberGenerator


NumberGenerator.java

import java.util.ArrayList;

import java.util.Iterator;

public abstract class NumberGenerator {
private ArrayList observers = new ArrayList();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
Iterator iterator = observers.iterator();
while (iterator.hasNext()) {
Observer obserber = (Observer)iterator.next();
obserber.update(this);
}
}

public abstract int getNumber();
public abstract void execute();
}


実際に数を生成するための処理execute()と数を取得するgetNumber()はサブクラスに実装させる設計となっています。

ここでは観察者のインスタンスを管理するためにobserversというArrayListを保持します。実際に数が生成されたタイミングで観察者に通知する場合は、notifyObservers()observersのインスタンスに対してupdate()呼び出しています。

Observerのインスタンスの管理はaddObserver()deleteObserver()で行います。


ConcreteObserver


DegitObserver


DegitObserver.java

public class DegitObserver implements Observer {

public void update(NumberGenerator generator) {
System.out.println("DegitObserver:" + generator.getNumber());
try {
Thread.sleep(100);
} catch (InterruptedException e) {

}
}
}


Observerの具象クラスになります。

実際にNumberGeneratorの数が更新された際の処理を実装しています。

ここでは、単に数を出力しています。出力時に更新される様子がわかりやすいように、Thread.sleep(100)で処理を遅延させています。


GraphObserver


GraphObserver.java

public class GraphObserver implements Observer {

public void update(NumberGenerator generator) {
System.out.print("GraphObserver:");
int count = generator.getNumber();
for (int i = 0; i < count; i++) {
System.out.print("*");
}
System.out.println("");
try {
Thread.sleep(100);
} catch (InterruptedException e) {

}
}
}


ここではgeneratorの数分記号を出力させています。


ConcreteSubject


RandomNumberGenerator


RandomNumberGenerator.java

import java.util.Random;

public class RandomNumberGenerator extends NumberGenerator {
private Random random = new Random();
private int number;

public int getNumber() {
return number;
}

public void execute() {
for (int i = 0; i < 20; i++) {
number = random.nextInt(50);
notifyObservers();
}
}
}


Subjectの具象クラスです。ここでは、abstractメソッドを実装しています。

今回は簡単に乱数を発生させてnumberが更新されたタイミングでnotifyObservers()を呼び出しています。


実行結果

DegitObserver:20

GraphObserver:********************
DegitObserver:41
GraphObserver:*****************************************
DegitObserver:41
GraphObserver:*****************************************
DegitObserver:35
GraphObserver:***********************************
DegitObserver:16
GraphObserver:****************
DegitObserver:49
GraphObserver:*************************************************
DegitObserver:44
GraphObserver:********************************************
DegitObserver:5
GraphObserver:*****
DegitObserver:0
GraphObserver:
DegitObserver:11
GraphObserver:***********
DegitObserver:16
GraphObserver:****************
DegitObserver:43
GraphObserver:*******************************************
DegitObserver:11
GraphObserver:***********
DegitObserver:47
GraphObserver:***********************************************
DegitObserver:24
GraphObserver:************************
DegitObserver:30
GraphObserver:******************************
DegitObserver:21
GraphObserver:*********************
DegitObserver:15
GraphObserver:***************
DegitObserver:20
GraphObserver:********************
DegitObserver:46
GraphObserver:**********************************************

それぞれのObserverが数を出力できているのがわかるかと思います。


SubjectObserverの関係性

ConcreteSubjectは状態が変化した際に通知を行うだけで、Observerの具体的的なインスタンスと処理を気にする必要がありません。同様に、ConcreteObserverSubjectの具体的なインスタンスを知る必要がないです。

抽象クラスのSubjectObserverということだけ知っておけば、通知されることと、その際に更新処理を行うことだけは担保されます。

この関係性を保って設計を行えば、具象クラスであるConcreteSubjectConcreteObserverのクラスはいくらでも変更が可能になります。再利用可能な部品を扱うというデザインパターンの思想に準拠しているわけですね。

また、今回の場合はupdate()の引数としてSubjectNumberGeneratorインスタンスを渡していましたが、numberのInt型を渡しても正常に動作します。値を渡す場合は、何が更新されたかの可読性は高まりますが汎用性は低下します。

一方、今回のようなNumberGeneratorのみの場合はSubjectのインスタンスを渡さなくても大丈夫ですが、Subjectが複数の場合はSubjectのインスタンスを判定するためにSubjectの情報が必要になります。このようにケースによって、update()の簡略化や複雑化を考えて設計する必要があります。


Observerの順序について

今回は更新された際、先に登録したDegitObserverが処理されるようになっていますが、一般的にはObserverupdateを呼ぶ順番が変わっても問題が起きないような設計にする必要があります。そのために、各クラスの独立性は常に保つように設計することが大切です。


ObserverSubjectの状態を変化させる場合

今回はSubject自身が値を更新し、update()を呼び出していましたが、update()を呼び出すきっかけがObserverである場合もありえます。GUIにおけるview操作がそれにあたる。ボタンを押した際、や画面をスワイプした際にデータを更新する場合などがそれにあたるとおもいます。この場合、SubjectObserverの設計を気をつけないと、以下のようなフローが発生する可能性があります。



  1. Subjectの状態が変化


  2. Observerへ通知


  3. ObserverSubjectのメソッドを呼び出す。


  4. Subjectの状態が変化


  5. Observerへ通知...

この場合は通知されてる最中かどうかのフラグをもたせたり、Subjectの状態を変えないような設計にする必要があります。


Push型とPull

通知の方法ですが、一般的にPull型とPush型に大別されます。



  • Pull: 状態変化時にObserverSubjectに状態を問い合わせる


  • Push: 状態変化時にSubjectObserverに通知する

今回のサンプルケースはPush型のパターンでしたね。Push型の場合は今回のように引数がNumberGeneratorだったように状態の変化を直接知ることができますが一般化には向いてないですね。

一方Pull型はupdate()を一般化しやすいですが、処理が複雑になりがちです。


Pull型の例

public void update() {

// Observer自身が問い合わせる
System.out.print("Number:" + this.subject.number);
}


サンプルコードについて

以下のレポジトリにソースコードをアップしてあります。

shoheiyokoyama/design-pattern_java


デザインパターン


参考文献