daisukeの技術ブログ

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

Javaでデザインパターンを学ぶ:Observerパターン

Javaによるデザインパターンの3回目です!

教材は、参考文献の「Java言語で学ぶデザインパターン入門第3版」を使わせて頂きます。

今回は、Observerパターンです。それでは、早速やっていきましょう!

参考文献

参考文献のサンプルプログラムのダウンロード

www.hyuki.com

はじめに

「Javaでデザインパターンを学ぶ」の記事一覧です。良かったら参考にしてください。

Javaでデザインパターンの記事一覧

先に、23種類のデザインパターンを示します。

参考サイト:デザインパターン (ソフトウェア) - Wikipedia

デザインパターン一覧
◆生成に関するパターン
 ・Abstract Factory:関連するインスタンスを状況に応じて、適切に生成する方法を提供する
 ・Builder:複合化されたインスタンスの生成過程を隠蔽する。
 ・Factory Method:生成されるインスタンスに依存しない、インスタンスの生成方法を提供する
 ・Prototype:同様のインスタンスを生成するために、原型のインスタンスを複製する
 ・Singleton:あるクラスについて、インスタンスが1つしか存在しないことを保証する
◆構造に関するパターン
 ・Adapter:元々関連性のない2つのクラスを接続するクラスを作る
 ・Bridge:クラスと呼び出し側の間の橋渡しをするクラスで、実装を隠蔽する
 ・Composite:再帰的な構造を表現する
 ・Decorator:あるインスタンスに対し、動的に付加機能を追加する
 ・Facade:複数のサブシステムの窓口となる共通のインタフェースを提供する
 ・Flyweight:多数のインスタンスを共有し、インスタンスの構築のための負荷を減らす
 ・Proxy:共通のインタフェースを持つインスタンスを内包し、利用者からのアクセスを代理する。Wrapperとも呼ばれる
◆振る舞いに関するパターン
 ・Chain of Responsibility:イベントの送受信を行う複数のオブジェクトを鎖状につなぎ、それらの間をイベントが渡されていくようにする
 ・Command:複数の異なる操作について、それぞれに対応するオブジェクトを用意し、オブジェクトを切り替えることで、操作の切り替えを実現する
 ・Interpreter:構文解析のために、文法規則を反映するクラス構造を作る
 ・Iterator:複数の要素を内包するオブジェクトのすべての要素に対して、順番にアクセスする方法を提供する
 ・Mediator:オブジェクト間の相互作用を仲介するオブジェクトを定義し、オブジェクト間の結合度を低くする
 ・Memento:データ構造に対する一連の操作のそれぞれを記録しておき、以前の状態の復帰または操作の再現が行えるようにする
 ・Observer:インスタンスの変化を他のインスタンスから監視できるようにする
 ・State:オブジェクトの状態を変化させることで、処理内容を変えられるようにする
 ・Strategy:データ構造に対して適用する一連のアルゴリズムをカプセル化し、アルゴリズムの切り替えを容易にする
 ・Template Method:アルゴリズムは抽象クラスで、処理内容はサブクラスで定義する
 ・Visitor:データ構造を保持するクラスと、それに対して処理を行うクラスを分離する

今回は、「Observerパターン」をやっていきます!

サンプルコードの理解

Observerパターンは、Observer(観察者)インタフェースを持った観察者たちが、対象のオブジェクトの状態が変化したときに、通知が得られる仕組みです。

これだけ聞いても、何がいいのか分からないですね。なので、早速サンプルコードを見ていくことにします。

今回のPlantUMLのクラス図

PlantUMLで作成したクラス図のコードと画像ファイルです。

@startuml
interface Observer {
    + {abstract} void update()
}

class DigitObserver {
    + void update()
}
Observer <|.. DigitObserver

class GraphObserver {
    + void update()
}
Observer <|.. GraphObserver

abstract NumberGenerator {
    - List<Observer> observers
    + void addObserver()
    + void deleteObserver()
    + void notifyObservers()
    + {abstract} int getNumber()
    + {abstract} void execute()
}

class RandomNumberGenerator {
    - Random random
    - int number
    + int getNumber()
    + void execute()
}
NumberGenerator <|-- RandomNumberGenerator
@enduml

今回のクラス図
今回のクラス図

Mainクラス(使う側のクラス)

まずは、Observerパターンが、どう使われるのかを見るために、Mainクラスを見ていきます。

分かりやすくなると思うので、先に実行結果も示します。

public class Main {
    public static void main(String[] args) {
        NumberGenerator generator = new RandomNumberGenerator();
        Observer observer1 = new DigitObserver();
        Observer observer2 = new GraphObserver();
        generator.addObserver(observer1);
        generator.addObserver(observer2);
        generator.execute();
    }
}

実行結果

実行結果
実行結果

Observer(観察者)は、インタフェースで、DigitObserver クラスと、GraphObserver クラスの2人が、そのインタフェースを実装しています。

一方、観察される側は、抽象クラスが NumberGenerator クラスで、サブクラスが RandomNumberGenerator クラスです。

実行結果を見ると、乱数生成器が、乱数を生成したときに、2人の観察者に通知を送っているようです。2人の観察者は、通知を受け取ると、それぞれの表現方法で、乱数の値を表示しているようです。

では、それぞれのソースコードを見ていきます。

Observerインタフェース

public interface Observer {
    public abstract void update(NumberGenerator generator);
}

Observer クラスを継承したサブクラス(観察者)は、観察対象の NumberGenerator クラスを継承した RandomNumberGenerator クラスに自分自身を登録します。

RandomNumberGenerator クラスは乱数を生成すると、観察者全員の update メソッドを使って通知します。

DigitObserverクラス

次は、DigitObserverクラスです。

public class DigitObserver implements Observer {
    @Override
    public void update(NumberGenerator generator) {
        System.out.println("DigitObserver:" + generator.getNumber());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    }
}

DigitObserverクラスの update() では、通知があった際に、単純に乱数の値を表示しています。

GraphObserverクラス

次は、GraphObserverクラスです。

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("");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    }
}

GraphObserverクラスの update() では、通知があった際に、乱数の値の数だけ、* を出力しています。

NumberGeneratorクラス

import java.util.ArrayList;
import java.util.List;

public abstract class NumberGenerator {
    // 保持しているObserverたち
    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 o: observers) {
            o.update(this);
        }
    }

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

    // 数を生成する
    public abstract void execute();
}

NumberGeneratorクラスは、プライベートメンバ変数として、観察者を登録できる observers(リスト)を持っています。

addObserver() は、引数で渡された観察者をリストに追加します。deleteObserver() は、引数で渡された観察者を削除します。notifyObservers() は、リストの観察者全員に、自分自身(インスタンス)を引数として、通知(観察者に update() をコール)を行います。

抽象メソッドとして、getNumber() と、execute() があります。

RandomNumberGeneratorクラス

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(50);
            notifyObservers();
        }
    }
}

NumberGeneratorクラスを継承した、サブクラスのRandomNumberGeneratorクラスは、プライベートメンバ変数として、random(乱数生成器)と、number(現在の数)を持ちます。

getNumber() は、現在の数を返します。

execute() は、乱数生成して、その値を number に設定し、全観察者に通知を行います。random.nextInt(50) は、0から49までのランダムな整数を生成してくれます。

おわりに

今回はObserverパターンを学びました。

観察するクラスに変化があった場合に、観察者が通知を受け取れる仕組みでした。観察者が増えたり、減ったりすることも簡単に実現できます。

また、Observer は、インタフェースとして定義しているので、別の観察者(別のクラス)が必要になった場合でも、容易に対応できます。

今回は以上です!

最後までお読みいただき、ありがとうございました。