Help us understand the problem. What is going on with this article?

デザインパターン「Observer」

More than 3 years have passed since last update.

はじめに

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

Observerパターン

スクリーンショット 2016-11-03 23.39.42.png

Observerパターンとは

観察される側(=Subject)と観察する側(=Observer)の2つの役割が存在し、Subjectの状態が変化した際にObserverに通知されるデザインパターンです。そのため、状態変化に応じた処理を記述する時に有効です。
どちらかというと観察よりも通知に重点が置かれており、Observerが能動的に通知を待っているのでPublish-Subscribeパターンと呼ばれることがあります。Publish(=発行)とSubscribe(=購読)という意味でこちらの方がイメージつきやすいかもしれませんね。
MVC(Model / View / Controller)アーキテクチャでいう。ModelViewの関係性に適応される場合が多いです。

Observerパターンのクラス図

Observerクラス図 2016-02-14 15.53.14.png

  • 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

デザインパターン

参考文献

shoheiyokoyama
【横山 祥平 / @shoheiyokoyama 】 CyberAgent, Inc / AbemaTV / CATS / iOS Engineer Medium: https://medium.com/@shoheiyokoyama
https://github.com/shoheiyokoyama
cyberagent
サイバーエージェントは「21世紀を代表する会社を創る」をビジョンに掲げ、インターネットテレビ局「AbemaTV」の運営や国内トップシェアを誇るインターネット広告事業を展開しています。インターネット産業の変化に合わせ新規事業を生み出しながら事業拡大を続けています。
http://www.cyberagent.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした