はじめに
本投稿はJava言語で学ぶデザインパターン入門のデザインパターンをまとめた記事です。今回はObserverパターンになります。
まとめ一覧はこちら
Observerパターン
Observerパターンとは
観察される側(=Subject)と観察する側(=Observer)の2つの役割が存在し、Subject
の状態が変化した際にObserver
に通知されるデザインパターンです。そのため、状態変化に応じた処理を記述する時に有効です。
どちらかというと観察よりも通知に重点が置かれており、Observer
が能動的に通知を待っているのでPublish-Subscribe
パターンと呼ばれることがあります。Publish
(=発行)とSubscribe
(=購読)という意味でこちらの方がイメージつきやすいかもしれませんね。
MVC
(Model
/ View
/ Controller
)アーキテクチャでいう。Model
とView
の関係性に適応される場合が多いです。
Observerパターンのクラス図
-
Subject
とObserver
の2役のみ - 状態が変化した際に
Observer
に通知を行う。
Observerパターンのサンプル
抽象クラス
- Observer
- Observer
- Subject
- NumberGenerator
具象クラス
- ConcreteObserver
- DegitObserver
- GraphObserver
- ConcreteSubject
- RandomNumberGenerator
今回のサンンプルは、数を生成するSubject
をObserver
が観察し、生成したタイミングでObserver
がその値を表示するという簡易的なものです。
Main
クラス
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();
}
ランダムな数を生成するRandomNumberGenerator
をdegitObserver
とgraphObserver
が観察し、数字が変化したタイミングでdegitObserver
は値を、graphObserver
が値を簡易グラフで出力します。
Observer
Observer
public interface Observer {
public abstract void update(NumberGenerator generator);
}
Observer
のインターフェイスクラスです。実際観察するクラスはこのインターフェイスを実装します。数が生成されたタイミングで処理をするための、update
メソッドのみになります。
Subject
NumberGenerator
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
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
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
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
が数を出力できているのがわかるかと思います。
Subject
とObserver
の関係性
ConcreteSubject
は状態が変化した際に通知を行うだけで、Observer
の具体的的なインスタンスと処理を気にする必要がありません。同様に、ConcreteObserver
はSubject
の具体的なインスタンスを知る必要がないです。
抽象クラスのSubject
とObserver
ということだけ知っておけば、通知されることと、その際に更新処理を行うことだけは担保されます。
この関係性を保って設計を行えば、具象クラスであるConcreteSubject
とConcreteObserver
のクラスはいくらでも変更が可能になります。再利用可能な部品を扱うというデザインパターンの思想に準拠しているわけですね。
また、今回の場合はupdate()
の引数としてSubject
のNumberGenerator
インスタンスを渡していましたが、number
のInt型を渡しても正常に動作します。値を渡す場合は、何が更新されたかの可読性は高まりますが汎用性は低下します。
一方、今回のようなNumberGenerator
のみの場合はSubject
のインスタンスを渡さなくても大丈夫ですが、Subject
が複数の場合はSubject
のインスタンスを判定するためにSubject
の情報が必要になります。このようにケースによって、update()
の簡略化や複雑化を考えて設計する必要があります。
Observer
の順序について
今回は更新された際、先に登録したDegitObserver
が処理されるようになっていますが、一般的にはObserver
のupdate
を呼ぶ順番が変わっても問題が起きないような設計にする必要があります。そのために、各クラスの独立性は常に保つように設計することが大切です。
Observer
がSubject
の状態を変化させる場合
今回はSubject
自身が値を更新し、update()
を呼び出していましたが、update()
を呼び出すきっかけがObserver
である場合もありえます。GUI
におけるview
操作がそれにあたる。ボタンを押した際、や画面をスワイプした際にデータを更新する場合などがそれにあたるとおもいます。この場合、Subject
とObserver
の設計を気をつけないと、以下のようなフローが発生する可能性があります。
-
Subject
の状態が変化 -
Observer
へ通知 -
Observer
がSubject
のメソッドを呼び出す。 -
Subject
の状態が変化 -
Observer
へ通知...
この場合は通知されてる最中かどうかのフラグをもたせたり、Subject
の状態を変えないような設計にする必要があります。
Push
型とPull
型
通知の方法ですが、一般的にPull
型とPush
型に大別されます。
-
Pull
: 状態変化時にObserver
がSubject
に状態を問い合わせる -
Push
: 状態変化時にSubject
がObserver
に通知する
今回のサンプルケースはPush型のパターンでしたね。Push型の場合は今回のように引数がNumberGenerator
だったように状態の変化を直接知ることができますが一般化には向いてないですね。
一方Pull型はupdate()
を一般化しやすいですが、処理が複雑になりがちです。
public void update() {
// Observer自身が問い合わせる
System.out.print("Number:" + this.subject.number);
}
サンプルコードについて
以下のレポジトリにソースコードをアップしてあります。
shoheiyokoyama/design-pattern_java
デザインパターン
- 生成に関するパターン
- Abstract factory
- Builder
- Factory Method
- Prototype
- Singleton
- 構造に関するパターン
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- 振る舞いに関するパターン
- Chain of responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor