Robotlegs2のMessageDispatcherを試してみる


[前回の続き](http://qiita.com/YuukiOgino/items/775ac3c75a5d25bd3db1)です。

Robotlegs2は[MessageDispatcher](https://github.com/robotlegs/robotlegs-framework/blob/master/src/robotlegs/bender/framework/impl/MessageDispatcher.as)という機能があるのですが、探しても記事が無いので自分で試してみました。

#MessageDispatcherとは？
MessageDispatcherは登録したエンドポイントへString型のメッセージを送る機能です。
[Spring Framework](https://projects.spring.io/spring-framework/)にありますね。

使い方はEventに似ています。
Eventと違うのは、より不特定多数に同時に通知させるのに適しています。

#実装サンプル
「クリックしてください」のテキストをクリックすると「変更しました」と表示する単純なアプリです。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/104377/a264d2a8-bfd4-62fa-a22e-3de763065793.png)

![2.png](https://qiita-image-store.s3.amazonaws.com/0/104377/b9cc9fd1-77ac-d0f8-a497-77bdcc33bb71.png)

今回のサンプルは[ここからダウンロード](https://github.com/YuukiOgino/Robotlegs2MessageSample)できます。

※サンプルはMVCモデルで実装しています。
※[FlashDevelop](http://flashdevelop.jp/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8)を使用しています。

#サンプルコードの説明

[前回のサンプルコード](https://github.com/YuukiOgino/Robotlegs2Sample)に少し手を加えました。
変更を入れていないソースは説明を省略します。

## Config

```lang:MainConfig.as
	/**
	 * メイン画面のコンフィギュレーションを行うクラス
	 * 
	 * このクラスで以下の設定を実行しています。
	 * 	・UserModel はシングルトンである
	 * 	・UserView の対になるメディエータは UserViewMediator である
	 * 　・メディエーターでUserChangeEvent.USER_CHANGEが呼ばれた場合、UserCommondを実行する
	 * 
	 * @author ogino
	 */
	public class MainConfig implements IConfig 
	{
	
		//-----------------------------------------------------
		//コンポーネント
		//-----------------------------------------------------			
		
		/** 画面 */
		private var userView:UserView = new UserView();		
		
		//-----------------------------------------------------
		//プロパティ
		//-----------------------------------------------------			
		
		[Inject]
		/** インジェクター */
		public var injector:IInjector;
		
		[Inject]
		/** メディエーターマップ */
		public var mediatorMap:IMediatorMap;
		
		[Inject]
		/** イベントコマンドマップ */
		public var commandMap:IEventCommandMap;				
		
		[Inject]
		/** コンテキストビュー */
		public var contextView:ContextView;
		
		//-----------------------------------------------------
		//メソッド
		//-----------------------------------------------------			
		
		/**
		 * コンフィギュレーション実行
		 */
		public function configure():void 
		{
			// おまじない
			
			// MessageDispatcherはシングルトンである
			injector.map(MessageDispatcher).asSingleton();
			
			// UserModel はシングルトンである
			injector.map(UserModel, "modelName").asSingleton();
			injector.map(UserModel).asSingleton();
			
			// UserView の対になるメディエータは UserViewMediator である
			mediatorMap.map(UserView).toMediator(UserViewMediator);
			
			// メディエーターでUserChangeEvent.USER_CHANGEが呼ばれた場合、UserCommondを実行する
			commandMap.map(UserChangeEvent.USER_CHANGE).toCommand(UserCommand);
			
			// このような形で値を注入することも可能
			//var user:UserModel = new UserModel();
			//injector.map(UserModel, "modelName").toValue(user);			
			
			// おまじないを設定すると、下記の処理が出来る
			
			// 画面を切り替える　このプロジェクトは一つしか画面がないので、このメソッドで初期化処理を実行
			contextView.view.addChild(userView);
			
			// DIコンテナからモデルを取得
			var m:UserModel = injector.getInstance(UserModel, "modelName");
			m.name = "テスト表示";
		}
		
	}

```

***
Configクラスでrobotlegs.bender.framework.impl.MessageDispatcherをシングルトン登録します。

```lang:
injector.map(MessageDispatcher).asSingleton();
```

##Message
MessageDispatcherからMessageを受け取れるようにクラスを作成します。

まずMessageの基礎クラスを作成します。
この基礎クラスはEventクラスみたいなものです。

```lang:Message.as
    /**
     * MessageDispatcherによってやり取りされるメッセージの基本クラスです。
     *
     * @author ogino
     */
    public class Message
	{

		//-----------------------------------------------------
		//プロパティ
		//-----------------------------------------------------			
		
        /** メッセージタイプ */
        public var type:String;

		//-----------------------------------------------------
		//コンストラクタ
		//-----------------------------------------------------			
		
        /**
         * コンストラクタです。
         *
         * @param type メッセージタイプ
         */
        public function Message(type:String)
		{
            this.type = type;
        }

		//-----------------------------------------------------
		//メソッド
		//-----------------------------------------------------			
		
        /**
         * オブジェクトのストリング表現を返します。
         *
         * @return オブジェクトのストリング表現
         */
        public function toString():String 
		{
            var qualifiedClassName:String = getQualifiedClassName(this);
            var className:String = qualifiedClassName.split("::")[1];
            return "[" + className + " (type = " + type + ")]";
        }
    }
```

次に以下のようにMessageを継承したクラスを作成します。

```lang:UserMessage.as
	/**
	 * ユーザー関連のメッセージクラス
	 * 
	 * @author ogino
	 */
	public class UserMessage extends Message 
	{
		
		//-----------------------------------------------------
		//クラス定数
		//-----------------------------------------------------			
		
		/** ユーザーデータ更新 */
		public static const UPDATE_USER_DATA:String = "updateUserData";
		
		//-----------------------------------------------------
		//コンストラクタ
		//-----------------------------------------------------			
		
		/**
		 * コンストラクタ
		 * 
		 * @param type String
		 */
		public function UserMessage(type:String) 
		{
            super(type);
        }
		
	}
```

カスタムイベントクラスを作るようなイメージです。
これでMessageDispatcherを使用する準備が整いました。

##Mediator

```lang:UserViewMediator.as
	/**
	 * ユーザー画面のメディエータクラス
	 * UserModelとUserViewの仲介を行う
	 * 「ユーザの名前が変更された」というきっかけで「ビューにその名前を反映する」タスクを実行するだけのクラス
	 * 
	 * @author ogino
	 */
	public class UserViewMediator extends Mediator
	{
		
		//-----------------------------------------------------
		//コンポーネント
		//-----------------------------------------------------			
		
		[Inject]
		/** ユーザ画面 */
		public var view:UserView;		
		
		//-----------------------------------------------------
		//プロパティ
		//-----------------------------------------------------			
		
		[Inject(name="modelName")]
		/** ユーザモデル */
		public var user:UserModel;
		
		[Inject]
		/** メッセージディスパッチャー */
		public var messageDispatcer:MessageDispatcher;		
		
		//-----------------------------------------------------
		//オーバーライドしたメソッド
		//-----------------------------------------------------			
		
		/**
		 * @inheritDoc
		 */
		public override function initialize():void
		{
			// 名前が変更されたときに呼び出すメソッドをセット
			view.addEventListener(UserChangeEvent.USER_CHANGE, view_userChange);
			
			// メッセージディスパッチャーにUserMessage.UPDATE_USER_DATAを受け取ったらコールバックを実行するようにセット
			messageDispatcer.addMessageHandler(UserMessage.UPDATE_USER_DATA, UserMessageHandler);
			
		}
		
		/**
		 * @inheritDoc
		 */
		public override function destroy():void
		{
			// 登録したメソッドを削除
			view.removeEventListener(UserChangeEvent.USER_CHANGE, view_userChange);
			
			// メッセージディスパッチャーから受け取るメッセージを削除する。これでUserMessage.UPDATE_USER_DATAは受信できなくなる
			messageDispatcer.removeMessageHandler(UserMessage.UPDATE_USER_DATA, UserMessageHandler);
		}
		
		//-----------------------------------------------------
		//メッセージハンドラー
		//-----------------------------------------------------			
		
		/**
		 * データ更新関連メッセージ　メッセージハンドラー
		 * 
		 * @param	message
		 */
		protected function UserMessageHandler(message:String):void 
		{
			switch (message) 
			{
				case UserMessage.UPDATE_USER_DATA: // このメッセージを受け取ったら画面更新する
					view.nameChange(user.name);
					break;
				default:
					break;
			}
			
		}

		//-----------------------------------------------------
		//イベントハンドラー
		//-----------------------------------------------------			
		
		/**
		 * 画面変更通知イベントハンドラー
		 * 
		 * @param	e
		 */
		private function view_userChange(e:UserChangeEvent):void 
		{
			// イベントをクローンする
			var newEvent:UserChangeEvent = UserChangeEvent(e.clone());
			// フレームワークに通知　このイベントに紐付けられたCommandクラスが呼ばれる
			eventDispatcher.dispatchEvent(newEvent);
		}		
	}
```
***

MessageDispatcherをRobotlegs側から注入します。

```lang:
[Inject]
public var messageDispatcer:MessageDispatcher;	
```

***

次にMessageDispatcherから特定のメッセージを受信した時に、コールバック関数を呼び出すように設定します。

```lang:
messageDispatcer.addMessageHandler(UserMessage.UPDATE_USER_DATA, UserMessageHandler);
```

***

特定のメッセージから受信したコールバック関数を削除するには、以下の通りに記述します。

```lang:
messageDispatcer.removeMessageHandler(UserMessage.UPDATE_USER_DATA, UserMessageHandler);
```

addEventListener、removeEventListenerと同じ使い方が出来ます。

***

コールバック関数は必ず**String型**の引数を**一つ**設定します。

```lang:

protected function UserMessageHandler(message:String):void 
{
	switch (message) 
	{
		case UserMessage.UPDATE_USER_DATA: 
			view.nameChange(user.name);
			break;
		default:
			break;
	}
			
}
```

受け取ったメッセージごとにswitch文で処理を切り分けています。
このメソッドはUserMessage.UPDATE_USER_DATAを受信したら画面を更新しています。

##Command

```lang:UserCommand.as
	/**
	 * ユーザーに関するコマンド実行
	 * このクラスはコンフィグで設定したイベントがメディエーターで呼ばれた場合、executeメソッドがフレームワーク側から実行される
	 * 他のクラスから、コマンドを直接実行させるのは控えること
	 * 
	 * @author ogino
	 */
	public class UserCommand extends Command
	{
		
		//-----------------------------------------------------
		//プロパティ
		//-----------------------------------------------------			
		
		[Inject]
		/** イベント　コマンドを実行したいイベントクラスをpublicで必ず記載すること */
		public var event:UserChangeEvent;		
		
		[Inject(name="modelName")]
		/** ユーザモデル */
		public var user:UserModel;		
		
		[Inject]
		/** メッセージディスパッチャー */
		public var messageDispatcer:MessageDispatcher;				
		
		//-----------------------------------------------------
		//オーバーライドしたメソッド
		//-----------------------------------------------------			
		
		/**
		 * @inheritDoc
		 */
		override public function execute():void 
		{
			user.name = "変更しました";
			
			// メッセージディスパッチャーにメッセージを通知
			messageDispatcer.dispatchMessage(UserMessage.UPDATE_USER_DATA);
		}
		
	}
```

***

MessageDispatcherをRobotlegs側から注入します。

```lang:
[Inject]
public var messageDispatcer:MessageDispatcher;	
```

***

モデルを更新したらMessageDispatcherにMessageを通知します。

```lang:
messageDispatcer.dispatchMessage(UserMessage.UPDATE_USER_DATA);	
```

これで、MessageDispatcherからUserViewMediator.UserMessageHandlerが呼ばれます。
UserViewMediator.UserMessageHandlerが呼ばれることにより、画面に表示されている文言が変更されます。

##Model
```lang:UserModel.as
	/**
	 * ユーザー名のモデルクラス
	 * 
	 * @author ogino
	 */
	public class UserModel
	{
		
		//-----------------------------------------------------
		//プロパティ
		//-----------------------------------------------------		
		
		//---------------
		//name
		//---------------	
		
		/**
		 * @private
		 */
		private var _name:String;
		
		/**
		 * ユーザー名
		 */
		public function get name():String { return _name; }
		
		/**
		 * @private
		 */
		public function set name(value:String):void 
		{
			_name = value;
		}
	}
```

地味ですがMessageDispatcherでMediatorに更新を通知できるようになったので、EventDispatcherが必要なくなりました。

***

以上、サンプルコードの説明でした。

MessageDispatcherを利用するとより柔軟なDIコンテナの開発が出来そうです。
あくまでも使用例の一つなので他にもこういう使い方ができるよ、というのがあれば教えてください。