[AS3] Observerパターンのサンプルプログラム [Edit]

オブジェクト指向プログラミングにおけるデザインパターンに、「Observerパターン」があります。対象オブジェクトの状態が変わるごとに、その情報を必要なインスタンス(Observer)に送る仕組みです。Javaの例をActionScript 3.0に書替えてみましょう。今回は、デザインパターンの解説書として定評のある結城浩著『Java言語で学ぶデザインパターン入門』のサンプルプログラムを題材とします[*1]

Observerパターンは、出版−購読型モデルとも呼ばれます。メールマガジンに登録して、その配信を受けるのと似た仕組みです。したがって、配信する側(Generator)と受取る側(Observer)のふたつの役割が必要になります。ActionScript 3.0のクラス設計では、インターフェイスで仕様を定めます。

01 配信側クラスの仕様 ー インターフェイスINumberGenerator

配信側のインターフェイスには、少なくとも3つのメソッドを備える必要があります(スクリプト001)。

  1. オブザーバーの登録: addObserver()
  2. オブザーバーの削除: deleteObserver()
  3. オブザーバーに配信: notifyObservers()
スクリプト001■インターフェイスINumberGenerator
package {
	public interface INumberGenerator {
		// 配信クラスに必要なメソッド
		function addObserver(observer:IObserver):void;
		function deleteObserver(observer:IObserver):void;
		function notifyObservers():void;
		// サンプルとして加えたメソッド
		function getNumber():int;
		function execute():void;
	}
}

Javaと異なりActionScript 3.0のインターフェイスでは宣言できませんが、受信するオブザーバーのインスタンス(後述インターフェイスIObserverを実装)を納めるプロパティも用意します。また、オブザーバーへの配信は、そのインスタンスのメソッド(後述update())を呼出して行います。

02 受信側クラスの仕様 ー インターフェイスIObserver

受信側のインターフェイスは、配信側クラスのメソッドnotifyObservers()から呼出されるupdate()を宣言します(スクリプト002)。

スクリプト002■インターフェイスIObserver
package {
	public interface IObserver {
		function update(generator:INumberGenerator):void;
	}
}

メソッドupdate()は、引数に配信するクラス(インターフェイスINumberGeneratorを実装)のインスタンスを受取ることにしました。したがって、必要な情報はこのインスタンスから(getNumber()メソッドにより)取出します。

03 配信側クラスの実装 ー クラスRandomNumberGenerator

配信側のクラスRandomNumberGeneratorは、つぎのようにインターフェイスINumberGeneratorを実装します(スクリプト003)。

スクリプト003■クラスRandomNumberGenerator
package {
	import flash.utils.setInterval;
	import flash.utils.clearInterval;
	public class RandomNumberGenerator implements INumberGenerator {
		private var observers:Vector.<IObserver>  = new Vector.<IObserver>();
		private var number:int;
		private var id:uint;
		private var interval:uint = 1000;
		public function RandomNumberGenerator() {}
		public function addObserver(observer:IObserver):void {
			observers.push(observer);
		}
		public function deleteObserver(observer:IObserver):void {
			var nLength:uint = observers.length;
			for (var i:uint = 0; i < nLength; i++) {
				var element:IObserver = observers[i];
				if (element == observer) {
					observers.splice(i, 1);
					break;
				}
			}
		}
		public function notifyObservers():void {
			var nLength:uint = observers.length;
			number = Math.floor(Math.random() * 50);
			for (var i:uint = 0; i < nLength; i++) {
				var observer:IObserver = observers[i];
				observer.update(this);
			}
		}
		public function getNumber():int {
			return number;
		}
		public function execute():void {
			clearInterval(id);
			id = setInterval(notifyObservers, interval);
		}
	}
}

受信するオブザーバーのインスタンスは、Vectorインスタンスのプロパティ(observers)に納めることにしました。オブザーバーの登録と削除のメソッドaddObserver()およびdeleteObserver()は、それに即した処理にしています。また、配信のメソッドnotifyObservers()は、オブザーバーのインスタンスすべてを取出して、自身を引数にそのupdate()メソッドを呼出します。そのとき、ランダムな整数(0〜49)がプロパティ(number)に設定されます。

メソッドexecute()は、配信を開始します。一定の時間間隔(1000ミリ秒)でメソッドnotifyObservers()を呼出します[*2]。受信側のオブザーバーインスタンスは、getNumber()メソッドでランダムな整数が取出せます。

04 受信側クラスの実装 ー クラスDigitObserver

受信するオブザーバーのクラスDigitObserverは、以下のようにインターフェイスIObserverを実装します(スクリプト004)。定義するのはメソッドupdate()のみです。

スクリプト004■クラスDigitObserver
package {
	public class DigitObserver implements IObserver {
		public function DigitObserver() {}
		public function update(generator:INumberGenerator):void {
			trace("DigitObserver: " + generator.getNumber());
		}
	}
}

メソッドupdate()は、引数に受取った配信側インスタンスのgetNumber()メソッドによりランダムな整数を取出し、trace()関数でその値を[出力]します。

これで必要なインターフェイスおよびクラスは揃いました。確認用のドキュメントクラスMainを以下のように定義して[ムービープレビュー]で試しましょう(スクリプト005)。ランダムな整数が1秒ごとに[出力]されます(図001)。

スクリプト005■ドキュメントクラスMain
package {
	import flash.display.Sprite;
	public class Main extends Sprite {
		public function Main() {
			var generator:INumberGenerator = new RandomNumberGenerator();
			var observer0:IObserver = new DigitObserver();
			generator.addObserver(observer0);
			generator.execute();
		}
	}

}

図001■ランダムな整数が1秒ごとに[出力]される FF1010301_001.gif

FF1010301_002.gif

04 もうひとつの受信側クラスを実装 ー クラスGraphObserver

デザインパターンのつねとして、同じインターフェイスさえ実装すれば、具体的な処理内容は変えられます。受信するオブザーバーのクラスをもうひとつGraphObserverとして定義しました(スクリプト006)。

スクリプト006■クラスGraphObserver
package {
	public class GraphObserver implements IObserver {
		public function GraphObserver() {}
		public function update(generator:INumberGenerator):void {
			var result_str:String = "GraphObserver: ";
			var count:int = generator.getNumber();
			for (var i:uint = 0; i < count; i++) {
				result_str += "*";
			}
			trace(result_str);
		}
	}
}

テスト用のサンプルですので、前掲クラスDigitObserverと変えた点はごくわずかです。取出したランダムな整数を数値でなく、"*"の数でグラフのように[出力]します(図002)。ドキュメントクラスMainには、つぎのスクリプト006のようにステートメントを加えます。

スクリプト007■修正したドキュメントクラスMain
package {
	import flash.display.Sprite;
	public class Main extends Sprite {
		public function Main() {
			var generator:INumberGenerator = new RandomNumberGenerator();
			var observer0:IObserver = new DigitObserver();
			var observer1:IObserver = new GraphObserver();
			generator.addObserver(observer0);
			generator.addObserver(observer1);
			generator.execute();
		}
	}
}

図002■ランダムな整数とそのグラフが1秒ごとに[出力]される FF1010301_003.gif

FF1010301_004.gif

なお、「Observer(オブザーバー)パターン」はイベントリスナーを例に説明しています。


[*1] ActionScript 3.0への書替えは、筆者の解釈にもとづきます。

[*2] イベントリスナーはObserverパターンなので使わずに、あえてsetInterval()メソッドを用いました。

コメント

この記事へのコメント

  1. 1.Exercise Balls(2010年12月16日 01:40)

    Thanks for an idea, you sparked at thought from a angle I hadn’t given thoguht to yet. Now lets see if I can do something with it.

この記事にコメントを書く

記事に対するテクニカルな質問はご遠慮ください(利用規約)。

その他の記事