Observerパターン[Java]

GoFデザインパターンについて学んでます.

今回は「Observer」パターンについて学びます.

Observer #とは

書籍には「観察者」と訳されていますが,ボクはシュタゲが好きなので「観測者」と呼びます.
Observer Patternでは,観測対象,つまり被験者(Subject)の状態が変化すると,
観測者(Observer)に対して通知されます.状態変化に応じた処理を記述するときに有効です.
Subjectは状態を持っている役で,Observerは状態変化を通知してもらう役です.

Subject-Observerの関係はTwitterで例えると,
アイドル(Subject)の日常生活の呟きをフォロワー(Observer)であるボクらが見ている感じ.

f:id:xrdnk:20200111214016p:plain

Sample ProgramのClass Diagramは以下.

f:id:xrdnk:20200111214030p:plain

ここでは
抽象Subject役:NumberGeneratorクラス
具象Subject役:RandomNumberGeneratorクラス
抽象Observer役:Observerインタフェース
具象Observer役:DigitObserverクラス,GraphObserverクラス
になります.

Observerインタフェース(抽象Observer役)

public interface Observer {

    /** 状態の更新を伝達 抽象クラス */
    public abstract void update(NumberGenerator generator);
}

update()で状態が更新されたことをSubject役(NumberGenerator)に教えてもらいます.

NumberGeneratorクラス(抽象Subject役)

import java.util.ArrayList;
import java.util.ListIterator;

public abstract class NumberGenerator {

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

    /**
     * Observerの追加
     * @param observer
     */
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    /**
     * Observerの削除
     * @param observer
     */
    public void deleteObserver(Observer observer) {
        observers.remove(observer);
    }

    /**
     * Observerへ通知
     */
    public void notifyObservers() {
        ListIterator<Observer> it = observers.listIterator();
        while(it.hasNext()) {
            Observer o = it.next();
            o.update(this);
        }
    }

    /** 数の取得 抽象クラス */
    public abstract int getNumber();

    /** 数の生成 抽象クラス */
    public abstract void execute();
}

Observer役を登録・削除するメソッドを持ちます.
notifyObservers()では,Observer全員に自分の状態が更新されたことを伝えます.

RandomNumberGeneratorクラス(具象Subject役)

import java.util.Random;

public class RandomNumberGenerator extends NumberGenerator {

    private Random random = new Random();

    private int number;

    @Override
    public int getNumber() {
        return number;
    }

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

execute()で乱数を20個生成し,その都度notifyObservers()で観測者に通知します.

DigitObserverクラス(具象Observer役1)

public class DigitObserver implements Observer {

    @Override
    public void update(NumberGenerator generator) {

        System.out.println("DigitObserver:" + generator.getNumber());
}

観測した数を「数字」で表示します.

GraphObserverクラス(具象Observer役2)

public class GraphObserver implements Observer {

    @Override
    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("");
    }
}

観測した数の分,アスタリスクを表示します.

Mainクラス

public class Main {

    public static void main(String[] args) {

        /** 被験者の生成 */
        NumberGenerator generator = new RandomNumberGenerator();

        /** 観測者の追加 */
        generator.addObserver(new DigitObserver());
        generator.addObserver(new GraphObserver());

        /** 被験者の行動 */
        generator.execute();
    }
}

結果例

DigitObserver:12
GraphObserver:************
DigitObserver:6
GraphObserver:******
DigitObserver:14
GraphObserver:**************
DigitObserver:19
GraphObserver:*******************
DigitObserver:27
GraphObserver:***************************
DigitObserver:14
GraphObserver:**************
DigitObserver:15
GraphObserver:***************
DigitObserver:0
GraphObserver:
DigitObserver:27
GraphObserver:***************************
DigitObserver:4
GraphObserver:****
DigitObserver:27
GraphObserver:***************************
DigitObserver:21
GraphObserver:*********************
DigitObserver:2
GraphObserver:**
DigitObserver:6
GraphObserver:******
DigitObserver:17
GraphObserver:*****************
DigitObserver:5
GraphObserver:*****
DigitObserver:10
GraphObserver:**********
DigitObserver:6
GraphObserver:******
DigitObserver:1
GraphObserver:*
DigitObserver:15
GraphObserver:***************

「観測」というより「通知させて貰っている」

Observer役は能動的に「観測」しているというよりかは,
Subject役から受動的に「通知」されているのを待っていることから,
Publish(発行)-Subscribe(購読) (Pub/Sub) Patternと呼ばれることがあるみたいです.

f:id:xrdnk:20200111214056p:plain

Subject役は「観測」できることから,Observableです.
ふむ.Reactive Extensionsで見かけたことのある用語ですね.いずれまたまとめたいです.

また,MVC(Model View Controller)PatternのModel-Viewの関係に対応しています.
一般に1つのModelに複数のViewが対応しています.

f:id:xrdnk:20200111214113p:plain

ゲームキューブのコントローラーを握り,コントローラーを通して,
マリオのモデルを操作して,そのモデルの動作をテレビのビューを通じて見るって感じだとわかりやすい?