한국어 | English | 日本語
Webアプリケーションエンジニア (経験8.8年)
技術・開発
engineering
ウェブフロントエンドと バックエンド開発を扱います

5. オブザーバーパターン

頻繁に変化する変数の状態をどのように追跡するのでしょうか?単に手動で確認するだけでなく、オブジェクト間の依存関係を最小限に抑えながら、状態の変化をリアルタイムでやり取りする効率的なメカニズムを考察します。
ObservableとObserverインターフェースを中心とした発行-購読モデルの動作原理を理解し、データ伝達方式であるPushとPullの違い、およびオブジェクト間の結合度を下げるデザイン原則を学習します。

変数

プログラミングを初めて学ぶとき、私たちはまず変数について学びます。変わらない値は定数(Constant)と呼ばれ、変数(Variable)の値は頻繁に変化します。変数は頻繁に変化するため、文字通りプログラム全体を通して様々な状況を経験します。

状態

変数は上記で述べたように、本当に多様な状態を持ちます。このような変数の状態を知るためには、二つの方法があります。

Push方式

自動: 変数が自身の状態が変わったことを私たちに知らせます

Pull方式

手動: 私たちが変数の状態が変わったかどうかを直接調べます

自動で私たちに知らせてくれるのが最も便利に見えるかもしれませんが、特に知る必要がないのに常に自身の状態を伝え続けてくるのであれば、非常に煩わしいでしょう。その状態を継続的に追跡するためのリソースも不必要に浪費されることになります。そのような場合は、私たちが必要なときにだけ状態を確認できる手動の方法も必要です。これを少々堅苦しい言い方でPush方式とPull方式と呼びます。変数の状態を一つの「主題」と見なすと、その主題を中心に私たちに知らせてくれるのか(Push方式)、それとも**私たちが調べるのか(Pull方式)**によって、状態を知る方法が分かれるのです。

オブザーバーパターン

オブザーバーパターンは、変数の状態を(PushとPullの中から選択した方式で)知ることができるパターンです。

Observer Shorthand

一般的にこのパターンを説明する際、状態を「主題」と見なし、Publish-Subscribe(発行-購読)モデルとして説明されることがあります。ここでは、パターン名がオブザーバーパターンであるため、混乱を避けるために購読モデルではなく、ObserverObservableの二つの用語のみで説明します。オブザーバーパターンには、先に述べた通り、ちょうど二種類のインターフェースしか存在しません。一つは状態を持っているObservable、もう一つは状態を見ようとするObserverです。

Observable

上記のオブザーバーパターン図を見ると、Observableインターフェースは二つの情報を持っています。

誤解してはならない点は、Observableインターフェースが状態そのものではなく、状態を「持っている」ということです。状態を持っているという意味でObservable、つまりObserverはこのObservableインターフェースを通じて状態を「見ることができる」という意味なのです。そしてObservableは、状態を知らせる/調べようとするObserverたちをリスト(もちろん他のデータ構造も可能です)で管理し、Push方式の場合は状態を誰に送るのか? そしてPull方式の場合は状態を誰だけが見ることができるのか? を決定できます。

interface Observable {
    protected List<Observer> observers; // Javaでは、これは通常実装クラスのインスタンス変数となる。
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObserver(Object obj);
}
class StateObservable implements Observable {
    private State state; // 'State'は定義済みのクラスまたは型と仮定
    private List<Observer> observers = new ArrayList<>(); // 一般的なObserver管理方法

    public void changeState() { /* 状態が変更されます。 */ }

    @Override
    public void registerObserver(Observer o) { /* オブザーバーを追加 */ observers.add(o); }
    @Override
    public void removeObserver(Observer o) { /* オブザーバーを除外 */ observers.remove(o); }
    // このメソッドはPushモデル用です。ここで'State'型がよく使われます。
    public void notifyObserver(State state) { /* 2. オブザーバーリストの各オブザーバーに1.状態を送信 */
        for (Observer o : observers) {
            // Observerが状態を受け取るための'update'メソッドを持っていると仮定。
            // o.update(state);
        }
    }
}
    // 純粋なPullモデルでは、オブザーバーが積極的にポーリングするため、このメソッドは通常何も行いません
    // または、オブザーバーにデータをプルするように促す一般的な通知として機能します。
    @Override
    public void notifyObserver(Object obj) { /* 何もしません。 */ }
    public State getState() { /* オブザーバーは状態を取得するためにこのメソッドを呼び出すことができます。 */ return state; }
}

Observer

Observerは、長く説明するまでもなく、状態を見ようとするインターフェースです。インターフェースであるため、その情報を参照し活用したいのであれば、意図に合わせて好きなように実装して使用すればよいです。

interface Observer {
    protected Observable observable; // Javaでは、これは通常実装クラスのインスタンス変数となる。
    public void getStateFromObservable(); // このメソッドはPullメカニズムを示します。
}
class StateObserver implements Observer {
    private State state;
    private Observable observable;

    public StateObserver(Observable observable) {
        this.observable = observable;
        this.observable.registerObserver(this);
    }

    // このメソッドはPush方式の更新でよく使われますが、
    // ここで定義されているObserverインターフェースはgetStateFromObservable()を定義しています。
    public void update(State state) { // State型を想定、Observable.notifyObserver()から呼ばれることが多い
        this.state = state;
    }

    @Override // インターフェースに従ったPullモデル用の実装
    public void getStateFromObservable() {
        // オブザーバーがそのObservable参照を使って状態をプルする場所です。
        // 例: this.state = observable.getState();
    }
}

なぜStateObserverをObservable.getObservers().add(new StateObserver())という方法で追加せず、StateObserverオブジェクトを生成する際にObservableを渡してコンストラクタ内で追加したのでしょうか?

Observable.getObservers()を呼び出さないことで、オブザーバーリストをObservableの外部に決して公開しないためです。


オブザーバーパターンは、なぜわざわざパターンとして定義されたのでしょうか?そこまで複雑にする必要はないように思えますが。その意義は、ObservableとObserverの二つのインターフェースが、互いの実装について全く知る必要なく、データのみをやり取りする点にあります。もう少し詳しく説明すると、以下のようになります。

Observableが持つ状態とオブザーバーテーブルの両方を外部に公開せず、オブザーバーのみがそれを知るようにすること。 「相互作用するオブジェクト間では、可能な限り疎結合なデザインを使用すべきである」という原則です。

ご理解いただけたでしょうか。今日のデザインパターンはここで終わりにします。


5. オブザーバーパターン
Author
Aaron
Posted on
Licensed Under
CC BY-NC-SA 4.0
CC BY-NC-SA 4.0
同じカテゴリーの関連記事
最新記事
LLMフィルターが奪う会話の筋肉とコミュニケーション様式
会話における無礼さを濾過し、洗練された回答を生成するLLMツールが日常化した現代において、私たちは本当に思慮深い会話をしているのだろうか?リアルタイムのコミュニケーションにおける数多くの失敗を通じて磨かれるべき会話能力が、外部ツールに依存することで退化している現象と、それがもたらす社会的な不安や世代間の行動様式の変化について考察する。
シニア採用における年俸交渉の最適なタイミングと戦略
年俸交渉は単なる数字の交換ではなく、心理的な駆け引きとタイミングが重要です。本稿では、企業側にとって、候補者が計算的な態度を取りがちな最終合格後よりも、採用プロセスの初期段階から段階的に交渉を進めることが、なぜより効率的であり、率直な情報の共有に繋がるのかを考察します。
法治主義の限界と人間の多様性
全ての人間の行為を単一の法体系で規制できるという信念は、傲慢であるかもしれない。この記事は、中世の階層的な統制から脱却し、現代の無限の自由を手に入れた人類が直面する法治主義の逆説と、多様性という名のもとに深化する社会的強制力と他者への悪魔化現象を鋭く分析する。
토스트 예시 메세지