オブザーバーの内面図

少し前にクラッチが切れたので、ジープを地元のディーラーに牽引してもらいました。ディーラーでは誰も知りませんでしたし、誰も私を知らなかったので、見積もりを知らせてくれるように電話番号を教えました。その配置は非常にうまく機能し、作業が終了したときに同じことを行いました。これはすべて私にとって完璧な結果だったので、ディーラーのサービス部門はほとんどの顧客と同じパターンを採用していると思います。

オブザーバーサブジェクトに登録し、その後通知を受信するこのパブリッシュ/サブスクライブパターンは、日常生活とソフトウェア開発の仮想世界の両方で非常に一般的です。実際、オブザーバーパターンは、知られているように、異なるオブジェクトが通信できるようにするため、オブジェクト指向ソフトウェア開発の要の1つです。この機能により、実行時にオブジェクトをフレームワークにプラグインできます。これにより、柔軟性が高く、拡張性があり、再利用可能なソフトウェアが可能になります。

注:この記事のソースコードは、リソースからダウンロードできます。

オブザーバーパターン

デザインパターン、著者はこのようなObserverパターンを説明します。

オブジェクト間の1対多の依存関係を定義して、1つのオブジェクトの状態が変更されたときに、そのすべての依存関係が自動的に通知および更新されるようにします。

オブザーバーパターンには1つのサブジェクトがあり、潜在的に多くのオブザーバーがいます。オブザーバーはサブジェクトに登録し、イベントが発生するとオブザーバーに通知します。代表的なオブザーバーの例は、単一モデルの2つのビューを同時に表示するグラフィカルユーザーインターフェイス(GUI)です。ビューはモデルに登録され、モデルが変更されると、ビューに通知され、それに応じて更新されます。それがどのように機能するか見てみましょう。

オブザーバーの活動

図1に示すアプリケーションには、1つのモデルと2つのビューが含まれています。画像の倍率を表すモデルの値は、スライダーノブを動かすことで操作されます。Swingのコンポーネントと呼ばれるビューは、モデルの値を示すラベルと、モデルの値に応じて画像を拡大縮小するスクロールペインです。

アプリケーション内のモデルは、インスタンスであるDefaultBoundedRangeModel()から有界整数値-このケースを追跡0する100これらの方法-with。

  • int getMaximum()
  • int getMinimum()
  • int getValue()
  • boolean getValueIsAdjusting()
  • int getExtent()
  • void setMaximum(int)
  • void setMinimum(int)
  • void setValue(int)
  • void setValueIsAdjusting(boolean)
  • void setExtent(int)
  • void setRangeProperties(int value, int extent, int min, int max, boolean adjusting)
  • void addChangeListener(ChangeListener)
  • void removeChangeListener(ChangeListener)

上記の最後の2つの方法が示すように、DefaultBoundedRangeModel()サポートのインスタンスはリスナーを変更します。例1は、アプリケーションがその機能をどのように利用するかを示しています。

例1.2人のオブザーバーがモデルの変更に反応する

importjavax.swing。*; importjavax.swing.event。*; importjava.awt。*; importjava.awt.event。*;インポートjava.util。*; public class Test extends JFrame { private DefaultBoundedRangeModel model = new DefaultBoundedRangeModel(100,0,0,100);プライベートJSliderスライダー=新しいJSlider(モデル); private JLabel readOut = new JLabel( "100%"); private ImageIcon image = new ImageIcon( "shortcake.jpg");プライベートImageViewimageView = new ImageView(image、model); public Test(){super( "オブザーバーデザインパターン");コンテナcontentPane = getContentPane(); JPanelパネル= new JPanel(); panel.add(new JLabel( "Set Image Size:")); panel.add(slider); panel.add(readOut); contentPane.add(panel、BorderLayout.NORTH); contentPane.add(imageView、BorderLayout.CENTER);model.addChangeListener(new ReadOutSynchronizer()); } public static void main(String args []){テストテスト= new Test(); test.setBounds(100,100,400,350); test.show(); }クラスReadOutSynchronizerはChangeListenerを実装します{ publicvoid stateChanged(ChangeEvent e){String s = Integer.toString(model.getValue()); readOut.setText(s + "%"); readOut.revalidate(); }}} class ImageView extends JScrollPane {private JPanel panel = new JPanel();プライベートディメンションoriginalSize = new Dimension();プライベート画像originalImage;プライベートImageIconアイコン; public ImageView(ImageIcon icon、BoundedRangeModel model){panel.setLayout(new BorderLayout()); panel.add(new JLabel(icon)); this.icon =アイコン; this.originalImage = icon.getImage(); setViewportView(panel);model.addChangeListener(new ModelListener()); originalSize.width = icon.getIconWidth(); originalSize.height = icon.getIconHeight(); }クラスModelListenerはChangeListenerを実装します{ publicvoid stateChanged(ChangeEvent e){BoundedRangeModel model =(BoundedRangeModel)e.getSource() ; if(model.getValueIsAdjusting()){int min = model.getMinimum()、max = model.getMaximum()、span = max --min、value = model.getValue();二重乗数=(double)value /(double)span;乗数=乗数== 0.0? 0.01:乗数;スケーリングされた画像= originalImage.getScaledInstance((int)(originalSize.width * multiplier)、(int)(originalSize.height * multiplier)、Image.SCALE_FAST); icon.setImage(scaled); panel.revalidate(); panel.repaint(); }}}}

スライダーノブを動かすと、スライダーはモデルの値を変更します。この変更により、モデルに登録されている2つの変更リスナーへのイベント通知がトリガーされ、読み取り値が調整され、画像が拡大縮小されます。両方のリスナーは、に渡された変更イベントを使用します

stateChanged()

モデルの新しい値を決定します。

Swingは、Observerパターンのヘビーユーザーです。押されたボタンへの反応から、内部フレームのウィンドウクローズイベントの拒否まで、アプリケーション固有の動作を実装するために50を超えるイベントリスナーを実装します。しかし、Swingは、Observerパターンを有効に活用する唯一のフレームワークではありません。Java2SDKで広く使用されています。例:Abstract Window Toolkit、JavaBeansフレームワーク、javax.namingパッケージ、および入出力ハンドラー。

例1は、SwingでのObserverパターンの使用を具体的に示しています。オブザーバーパターンの詳細について説明する前に、パターンが一般的にどのように実装されているかを見てみましょう。

オブザーバーパターンのしくみ

図2は、Observerパターンのオブジェクトがどのように関連しているかを示しています。

イベントソースであるサブジェクトは、オブザーバーのコレクションを維持し、そのコレクションにオブザーバーを追加および削除するメソッドを提供します。サブジェクトは、notify()登録された各オブザーバーに、オブザーバーが関心を持つイベントについて通知するメソッドも実装します。被験者は、オブザーバーのupdate()方法を呼び出してオブザーバーに通知します。

図3に、オブザーバーパターンのシーケンス図を示します。

通常、関連のないオブジェクトは、サブジェクトの状態を変更するサブジェクトのメソッドを呼び出します。その場合、サブジェクトは独自のnotify()メソッドを呼び出します。このメソッドは、オブザーバーのコレクションを反復処理して、各オブザーバーのupdate()メソッドを呼び出します。

オブザーバーパターンは、高度に分離されたオブジェクトが通信できるようにするため、最も基本的なデザインパターンの1つです。例1では、境界範囲モデルがリスナーについて知っているのは、リスナーがstateChanged()メソッドを実装していることだけです。リスナーはモデルの値にのみ関心があり、モデルの実装方法には関心がありません。モデルとそのリスナーはお互いについてほとんど知りませんが、オブザーバーパターンのおかげで彼らはコミュニケーションをとることができます。モデルとリスナーの間の高度な分離により、プラグ可能なオブジェクトで構成されるソフトウェアを構築できるため、コードの柔軟性と再利用性が向上します。

Java 2SDKとオブザーバーパターン

Java 2 SDKは、ディレクトリのObserverインターフェイスとObservableクラスを使用したObserverパターンの従来の実装を提供しますjava.utilObservableクラスは、被写体を表します。オブザーバーはObserverインターフェースを実装します。興味深いことに、この古典的なObserverパターンの実装は、サブジェクトがObservableクラスを拡張する必要があるため、実際にはめったに使用されません。この場合、継承を要求することは、潜在的にあらゆるタイプのオブジェクトがサブジェクト候補であり、Javaが多重継承をサポートしていないため、設計が不十分です。多くの場合、それらの科目候補者はすでにスーパークラスを持っています。

前の例で使用されたオブザーバーパターンのイベントベースの実装は、サブジェクトが特定のクラスを拡張する必要がないため、オブザーバーパターンの実装にとって圧倒的な選択肢です。代わりに、サブジェクトは、次のパブリックリスナー登録方法を必要とする規則に従います。

  • void addXXXListener(XXXListener)
  • void removeXXXListener(XXXListener)

サブジェクトのバインドされたプロパティ(リスナーによって監視されているプロパティ)が変更されるたびに、サブジェクトはリスナーを反復処理し、XXXListenerインターフェイスで定義されたメソッドを呼び出します。

これで、オブザーバーパターンを十分に把握できるはずです。この記事の残りの部分では、オブザーバーパターンの細かい点のいくつかに焦点を当てます。

匿名の内部クラス

例1では、内部クラスを使用してアプリケーションのリスナーを実装しました。これは、リスナークラスがそれらを囲むクラスに緊密に結合されているためです。ただし、リスナーは任意の方法で実装できます。ユーザーインターフェイスイベントを処理するための最も一般的な選択肢の1つは、匿名の内部クラスです。これは、例2に示すように、インラインで作成される名前のないクラスです。

例2.匿名の内部クラスを持つオブザーバーを実装する

... public class Test extends JFrame {... public Test(){... model.addChangeListener(new ChangeListener() {public void stateChanged(ChangeEvent e){String s = Integer.toString(model.getValue()) ; readOut.setText(s + "%"); readOut.revalidate();}}); } ...}クラスImageViewはJScrollPaneを拡張します{... public ImageView(final ImageIcon icon、BoundedRangeModel model){... model.addChangeListener(new ChangeListener() {public void stateChanged(ChangeEvent e){BoundedRangeModel model =(BoundedRangeModel)e.getSource(); if(model.getValueIsAdjusting()){int min = model.getMinimum()、max = model.getMaximum()、span = max --min、value = model.getValue(); 二重乗数=(double)value /(double)span; 乗数=乗数== 0.0?0.01:乗数; スケーリングされた画像= originalImage.getScaledInstance((int)(originalSize.width * multiplier)、(int)(originalSize.height * multiplier)、Image.SCALE_FAST); icon.setImage(scaled); panel.revalidate(); }}}); }}

例2のコードは、機能的には例1のコードと同等です。ただし、上記のコードでは、匿名の内部クラスを使用してクラスを定義し、一挙にインスタンスを作成します。

JavaBeansイベントハンドラ

前の例で示した匿名内部クラスの使用は開発者に非常に人気があったため、Java 2 Platform、Standard Edition(J2SE)1.4以降、JavaBeans仕様は、これらの内部クラスをクラスで実装およびインスタンス化する責任を負っていますEventHandler。例3に示すように:

例3.java.beans.EventHandlerの使用

インポートjava.beans.EventHandler; ... public class Test extends JFrame {... public Test(){... model.addChangeListener(EventHandler.create(ChangeListener.class、this、 "updateReadout")); } ... public void updateReadout() {String s = Integer.toString(model.getValue()); readOut.setText(s + "%"); readOut.revalidate(); }}..。