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

JavaでObserverパターン

はじめに

GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。

Observerパターン

Observerとは

「観察(observe)する人」のことで「観察者」を意味します。
観察する対象の状態が変化する際に観察者に対して通知を行うパターンのことをObserverパターンと言います。
詳しくは後述しますが、観察する対象が観察者に対して通知を行う必要があり、観察者は受動的な立場であることから、Publish-Subscribeパターンと呼ばれることもあるようです。

登場人物

Observerパターン使用するのは以下のクラス図に登場するクラスです。
image.png

抽象クラス

  • Subject
    観察される側となるクラスです。
    自身を観察するobservers(複数のobserver)とそれらを登録・削除・通知するメソッドや自身の状態を返すメソッドを持ちます。
    先ほど述べたように、観察される側であるはずsubjectが観察する側のobserverを保持している点が注意すべき点です。

  • Observer
    subjectを観察する側のクラスです。
    観察と言ってもsubjectから通知されて初めて観察対象の状態が変化したことを知ります。

実装クラス

  • ConcreteSubject
    具体的な観察される側となるクラスです。
    親クラスで宣言されている、自身の状態を返すgetSubjectStatusメソッドをオーバーライドします。

  • ConcreteObserveer
    具体的な観察する側となるクラスです。
    親クラスで宣言されている、観察対象の状態が変化したことを通知されるためのupdateメソッドをオーバーライドします。

具体例

具体例として、以下のクラスをもとに説明します。
image.png

抽象クラス

  • NumGeneratorクラス
NumGenerator.java
import java.util.ArrayList;
import java.util.List;

public abstract class NumGenerator {

    // ObserverのListを保持
    private List<Observer> observers = new ArrayList<>();

    // Observerを追加
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    // Observerを削除
    public void deleteObserver(Observer observer) {
        observers.remove(observer);
    }

    // Observerへ通知
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this);
        }
    }

    // 数を取得する
    public abstract int getNumber();

    // 数を生成する(状態の更新)
    public abstract void execute();
}

観察される対象のSubject役となるクラスです。
観察者は複数存在することもあり得るためObserverをListで持っています。
また、観察者を登録・削除するメソッドと、登録者に通知を行うためのメソッドも持っています。
notifyObserversメソッドでは具体的な観察者たちに対してupdateメソッドを呼び出しています。

  • Observerクラス
Observer.java
public interface Observer {
    public abstract void update(NumGenerator generator);
}

NumGeneratorを観察する役となるクラスです。
通知を受け取るためのupdateメソッドを宣言してますが、実装は継承するクラスに任せます。

実装クラス

  • RandomNumGeneratorクラス
RandomNumGenerator.java
import java.util.Random;

public class RandomNumGenerator extends NumGenerator {
    // 乱数生成機
    private Random random = new Random();
    // 現在の数
    private int number;

    // 数を取得する
    @Override
    public int getNumber() {
        return number;
    }

    // 数を生成する(状態の更新)
    @Override
    public void execute() {
        for (int i = 0; i < 10; i++) {
            // 0~20の範囲で乱数を生成して現在の数の更新
            number = random.nextInt(21);
            System.out.println("ループカウント:" + (i + 1));
            // 数が生成されたことをobserverへ通知
            notifyObservers();
        }
    }
}

NumGeneratorを継承するクラスで具体的な観察対象役となるクラスです。
乱数と現在の数を表すフィールドを持ち、数を取得するメソッドと数を生成するためのメソッドを持ちます。
数を生成するメソッドでは10回ループを回しており、各ループの中では0~20の範囲内で乱数を取得し、その乱数を現在の数値に設定することで状態の更新を行っています。
また、状態が更新されたことをnotifyObserversメソッドで各observerに通知します。

  • DigitObserverクラス
DigitObserver.java
public class DigitObserver implements Observer {
    @Override
    public void update(NumGenerator generator) {
        // 観察者が持つ乱数を取得して表示
        System.out.println("DigitObserver:" + generator.getNumber());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    }
}

Observerを実装する具体的な観察者の役となるクラスでupdateメソッドをオーバーライドしています。
このメソッドは観察対象が更新を通知してきた際に具体的な観察者が行う処理を記述します。
ここでは観察対象が持つ乱数(更新された現在の数)を表示します。

  • StringObserverクラス
StringObserver.java
import org.apache.commons.lang3.RandomStringUtils;

public class StringObserver implements Observer {
    @Override
    public void update(NumGenerator generator) {
        System.out.print("StringObserver:");
        // 観察者が持つ乱数を引数として、ランダムなアルファベット文字列を取得して表示
        System.out.println(RandomStringUtils.randomAlphabetic(generator.getNumber()));
        System.out.println("----------------------------------------");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    }
}

DigitObserverと同様に具体的な観察者役となるクラスです。
ここでは観察対象が持つ乱数(更新された現在の数)を引数として、ランダムなアルファベットからなる文字列を表示しています。

実行クラス

  • Mainクラス
Main.java
public class Main {
    public static void main(String[] args) {
        // 具体的な観察対象の生成
        NumGenerator generator = new RandomNumGenerator();
        // 具体的な観察対象に具体的な観察者を登録
        generator.addObserver(new DigitObserver());
        generator.addObserver(new StringObserver());
        // 数の生成(状態の更新)
        generator.execute();
    }
}

具体的な観察対象の生成と、具体的な観察者を観察対象に登録する処理を行った後、数の生成を行うことで状態を更新しています。

実行結果

Mainクラスを実行した結果は以下となります。
乱数と乱数の桁数分のランダムな文字列を表示を10回行っていることが分かります。
※乱数を利用しているため表示結果は異なります。

実行結果
ループカウント:1
DigitObserver:14
StringObserver:VNXxKnJCmkbOSG
----------------------------------------
ループカウント:2
DigitObserver:15
StringObserver:FUBpVQotKbSwmMX
----------------------------------------
ループカウント:3
DigitObserver:6
StringObserver:onRlXn
----------------------------------------
ループカウント:4
DigitObserver:18
StringObserver:AehtMZiGkzYgapTgok
----------------------------------------
ループカウント:5
DigitObserver:8
StringObserver:XuRUWXnb
----------------------------------------
ループカウント:6
DigitObserver:11
StringObserver:JHYAeuMfMDO
----------------------------------------
ループカウント:7
DigitObserver:12
StringObserver:sopRShHkheIO
----------------------------------------
ループカウント:8
DigitObserver:11
StringObserver:BLATKGBDccR
----------------------------------------
ループカウント:9
DigitObserver:18
StringObserver:kmSHMbZZftRyGkpaqa
----------------------------------------
ループカウント:10
DigitObserver:15
StringObserver:muYkfeGLfwYqykD
----------------------------------------

メリット

Observerパターンを利用することで、状態を保持するクラスと状態の変化を通知してもらうクラスを分けることができ、クラスの再利用性(部品化)が高まります。

使いどころ

Observerパターンの使いどころとして、MVCモデルの実装におけるモデルとビューの連携があげられます。
モデルは内部的なデータであり、コントローラーがモデル(観察対象)の状態の変化を検出し、ビュー(観察者)に通知することで連携を行います。

まとめ

オブジェクトの状態の変化を通知するObserverパターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。

参考文献

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
ユーザーは見つかりませんでした