オブザーバーとオブザーバブル

問題は次のとおりです。3次元シーンを記述するデータを2次元でレンダリングするプログラムを設計しています。プログラムはモジュール式である必要があり、同じシーンの複数の同時ビューを許可する必要があります。各ビューは、さまざまな照明条件の下で、さまざまな視点からシーンを表示できる必要があります。さらに重要なことに、基になるシーンの一部が変更された場合、ビューは自動的に更新される必要があります。

これらの要件はどれも、克服できないプログラミングの課題を提示しません。ただし、各要件を処理するコードを新たに作成する必要がある場合は、全体的な作業にかなりの作業が追加されます。幸い、これらのタスクのサポートは、JavaクラスライブラリによってインターフェイスObserverとクラスの形式ですでに提供されています。Observableどちらも、MVCアーキテクチャの要件に一部影響を受けています。

モデル/ビュー/コントローラー(MVC)アーキテクチャ

Model / View / Controllerアーキテクチャは、AlanKayによって発明された人気のあるオブジェクト指向プログラミング言語であるSmalltalkの一部として導入されました。MVCは、同じデータの複数の同期されたプレゼンテーションを利用するシステムを構築するために必要なプログラミングの労力を軽減するように設計されました。その中心的な特徴は、モデル、コントローラー、およびビューが個別のエンティティとして扱われ、モデルに加えられた変更が各ビューに自動的に反映される必要があることです。

上記の冒頭の段落で説明したプログラムの例に加えて、モデル/ビュー/コントローラーアーキテクチャは次のようなプロジェクトに使用できます。

  • 同じデータの棒グラフ、折れ線グラフ、および円グラフのビューを同時に含むグラフパッケージ。
  • デザインの一部をさまざまな倍率、さまざまなウィンドウ、さまざまなスケールで表示できるCADシステム。

図1は、MVCアーキテクチャを最も一般的な形式で示しています。1つのモデルがあります。複数のコントローラーがモデルを操作します。複数のビューにモデル内のデータが表示され、モデルの状態が変化すると変化します。

図1.モデル/ビュー/コントローラーアーキテクチャ

MVCの利点

モデル/ビュー/コントローラーアーキテクチャにはいくつかの利点があります。

  • プログラムのコンポーネント間には明確に定義された分離があります。各ドメインの問題は個別に解決できます。
  • 明確に定義されたAPIがあります。APIを適切に使用するものはすべて、モデル、ビュー、またはコントローラーのいずれかを置き換えることができます。
  • モデルとビューの間のバインドは動的です。コンパイル時ではなく、実行時に発生します。

MVCアーキテクチャーをデザインに組み込むことにより、プログラムの一部を個別にデザインし(そして、それらの仕事をうまく実行するようにデザインし)、実行時に一緒にバインドすることができます。後でコンポーネントが不適切であると判断された場合は、他の部品に影響を与えることなく交換できます。そのシナリオを、多くの手っ取り早いJavaプログラムに典型的なモノリシックアプローチと比較してください。多くの場合、フレームにはすべての状態が含まれ、すべてのイベントが処理され、すべての計算が実行され、結果が表示されます。したがって、そのようなシステムの最も単純なものを除いて、事後に変更を加えることは簡単ではありません。

パーツの定義

モデルは、プログラム内のデータを表すオブジェクトです。データを管理し、そのデータに対してすべての変換を実行します。モデルには、コントローラーまたはビューのいずれかに関する特定の知識がありません。どちらへの内部参照も含まれていません。むしろ、システム自体が、モデルとそのビューの間のリンクを維持し、モデルが変更されたときにビューに通知する責任を負います。

ビューは、モデルによって表されるデータの視覚的表示を管理するオブジェクトです。モデルオブジェクトの視覚的表現を生成し、データをユーザーに表示します。モデルオブジェクト自体への参照を介してモデルと対話します。

コントローラーは、モデルによって表されるデータとユーザーが対話するための手段を提供するオブジェクトです。これは、モデル内の情報またはビューの外観のいずれかに変更を加える手段を提供します。モデルオブジェクト自体への参照を介してモデルと対話します。

この時点で、具体的な例が役立つ場合があります。例として、はじめに説明したシステムを考えてみましょう。

図2.3次元視覚化システム

システムの中心となるのは、3次元シーンのモデルです。モデルは、シーンを構成する頂点と面の数学的記述です。各頂点または面を記述するデータは変更できます(おそらく、ユーザー入力またはシーンの歪みまたはモーフィングアルゴリズムの結果として)。ただし、視点、表示方法(ワイヤーフレームまたはソリッド)、遠近法、または光源の概念はありません。モデルは、シーンを構成する要素の純粋な表現です。

モデル内のデータをグラフィック表示に変換するプログラムの部分はビューです。ビューは、シーンの実際の表示を具体化します。これは、特定の照明条件下での特定の視点からのシーンのグラフィック表現です。

コントローラーは、モデルに対して何ができるかを認識しており、そのアクションを開始できるようにするユーザーインターフェイスを実装します。この例では、データ入力コントロールパネルを使用して、ユーザーが頂点と面を追加、変更、または削除できる場合があります。

オブザーバーとオブザーバブル

Java言語は、次の2つのクラスでMVCアーキテクチャをサポートします。

  • Observer:別のオブジェクトの状態が変化したときに通知を受け取りたいオブジェクト。
  • Observable:状態に関心がある可能性があり、別のオブジェクトが関心を登録する可能性があるオブジェクト。

これらの2つのクラスは、MVCアーキテクチャだけでなく多くの実装に使用できます。これらは、他のオブジェクトで発生した変更をオブジェクトに自動的に通知する必要があるシステムに適しています。

通常、モデルはのサブタイプでObservableあり、ビューはのサブタイプですObserver。これらの2つのクラスは、MVCの自動通知機能を処理します。これらは、モデルの変更をビューに自動的に通知できるメカニズムを提供します。コントローラとビューの両方でモデルへのオブジェクト参照により、モデル内のデータにアクセスできます。

オブザーバーとオブザーバブル関数

以下は、オブザーバーとオブザーバブル関数のコードリストです。

観察者

  • public void update(Observable obs, Object obj)

    オブザーバブルの状態に変化が生じたときに呼び出されます。

観察可能

  • public void addObserver(Observer obs)

    オブザーバーの内部リストにオブザーバーを追加します。

  • public void deleteObserver(Observer obs)

    オブザーバーの内部リストからオブザーバーを削除します。

  • public void deleteObservers()

    オブザーバーの内部リストからすべてのオブザーバーを削除します。

  • public int countObservers()

    オブザーバーの内部リスト内のオブザーバーの数を返します。

  • protected void setChanged()

    このオブザーバブルの状態が変化したことを示す内部フラグを設定します。

  • protected void clearChanged()

    このオブザーバブルの状態が変化したことを示す内部フラグをクリアします。

  • public boolean hasChanged()

    このオブザーバブルの状態が変化した場合、ブール値trueを返します。

  • public void notifyObservers()

    内部フラグをチェックして、オブザーバブルの状態が変化したかどうかを確認し、すべてのオブザーバーに通知します。

  • public void notifyObservers(Object obj)

    内部フラグをチェックして、オブザーバブルの状態が変化したかどうかを確認し、すべてのオブザーバーに通知します。パラメータリストで指定されたオブジェクトをnotify()オブザーバのメソッドに渡します。

次は、新たな作成方法を見てみましょうObservableObserver、クラス、および2を一緒に結び付ける方法。

オブザーバブルを拡張する

A new class of observable objects is created by extending class Observable. Because class Observable already implements all of the methods necessary to provide the desired behavior, the derived class need only provide some mechanism for adjusting and accessing the internal state of the observable object.

In the ObservableValue listing below, the internal state of the model is captured by the integer n. This value is accessed (and, more importantly, modified) only through public accessors. If the value is changed, the observable object invokes its own setChanged() method to indicate that the state of the model has changed. It then invokes its own notifyObservers() method in order to update all of the registered observers.

Listing 1. ObservableValue

 import java.util.Observable; public class ObservableValue extends Observable { private int n = 0; public ObservableValue(int n) { this.n = n; } public void setValue(int n) { this.n = n; setChanged(); notifyObservers(); } public int getValue() { return n; } } 

Implement an observer

A new class of objects that observe the changes in state of another object is created by implementing the Observer interface. The Observer interface requires that an update() method be provided in the new class. The update() method is called whenever the observable changes state and announces this fact by calling its notifyObservers() method. The observer should then interrogate the observable object to determine its new state, and, in the case of the MVC architecture, adjust its view appropriately.

In the following TextObserver listing, the notify() method first checks to ensure that the observable that has announced an update is the observable that this observer is observing. If it is, it then reads the observable's state, and prints the new value.

Listing 2. TextObserver

 import java.util.Observer; import java.util.Observable; public class TextObserver implements Observer { private ObservableValue ov = null; public TextObserver(ObservableValue ov) { this.ov = ov; } public void update(Observable obs, Object obj) { if (obs == ov) { System.out.println(ov.getValue()); } } } 

Tie the two together

A program notifies an observable object that an observer wishes to be notified about changes in its state by calling the observable object's addObserver() method. The addObserver() method adds the observer to the internal list of observers that should be notified if the state of the observable changes.

The example below, showing class Main, demonstrates how to use the addObserver() method to add an instance of the TextObserver class (Listing 2) to the observable list maintained by the ObservableValue class (Listing 1).

Listing 3. addObserver()

 public class Main { public Main() { ObservableValue ov = new ObservableValue(0); TextObserver to = new TextObserver(ov); ov.addObserver(to); } public static void main(String [] args) { Main m = new Main(); } } 

How it all works together

The following sequence of events describes how the interaction between an observable and an observer typically occurs within a program.

  1. First the user manipulates a user interface component representing a controller. The controller makes a change to the model via a public accessor method -- which is setValue() in the example above.
  2. The public accessor method modifies the private data, adjusts the internal state of the model, and calls its setChanged() method to indicate that its state has changed. It then calls notifyObservers() to notify the observers that it has changed. The call to notifyObservers() could also be performed elsewhere, such as in an update loop running in another thread.
  3. The update() methods on each of the observers are called, indicating that a change in state has occurred. The observers access the model's data via the model's public accessor methods and update their respective views.

Observer/Observable in an MVC architecture

Now let's consider an example demonstrating how observables and observers typically work together in an MVC architecture. Like the model in the ObservableValue (Listing 1) the model in this example is very simple. Its internal state consists of a single integer value. The state is manipulated exclusively via accessor methods like those in ObservableValue. The code for the model is found here.

Initially, a simple text view/controller class was written. The class combines the features of both a view (it textually displays the value of the current state of the model) and a controller (it allows the user to enter a new value for the state of the model). The code is found here.

By designing the system using the MVC architecture (rather than embedding the code for the model, the view, and the text controller in one monolithic class), the system is easily redesigned to handle another view and another controller. In this case, a slider view/controller class was written. The position of the slider represents the value of the current state of the model and can be adjusted by the user to set a new value for the state of the model. The code is found here.

About the author

Todd Sundsted has been writing programs since computers became available in desktop models. Though originally interested in building distributed object applications in C++, Todd moved to the Java programming language when Java became the obvious choice for that sort of thing.

This story, "Observer and Observable" was originally published by JavaWorld .