Robotlegs2 (RL2) はドキュメント不足と初代 Robotlegs のドキュメントが混在するため学習しづらいと感じ、僕なりに導入部分をまとめてみようと思います。
注)この記事では Robotlegs2 のことを単に Robotlegs と書いていることがあります。
Robotlegs の動作環境
Robotlegs は以下の様な環境で動作します。
- ActionScript3.0 を用いる
- Adobe Flash
- Flex
- Adobe AIR
- iOS/Android やデスクトップ環境で動く
Robotlegs の基本概念
Robotlegs は MVC フレームワークです。
特徴と言えるのは Dependency Injection (DI) を用いた制御の反転 (IoC) でしょう。
Dependency Injection (DI)
Robotlegs の DI のサンプルコードです。
public class Foo
{
[Inject]
public var bar:Bar;
}
var injector:Injector = new Injector;
// map
injector.map(Bar).asSingleton();
// inject
var foo:Foo = new Foo;
injector.injectInto(foo);
trace(foo.bar is Bar); // => true
ここでは Bar クラスを「シングルトン」としてインジェクタに登録し、そのインジェクタで Bar クラスのインスタンスのインジェクトを行っています。
Bar のインスタンスはこのインジェクタを用いて他のインスタンスに「注入」されているのです。
インジェクトされる先のメンバは [Inject] と書かれたメンバの中で型(クラス・インターフェイス)が一致するものです。
IoC with DI
制御の反転 (IoC) とは、「タスクの実行のきっかけを作った人は、実行するタスクも知っている」という「作った人」から「タスク」への依存関係を切り離して「タスクは、何々をきっかけに実行する」と置き換える依存関係の反転を指す言葉です。
タスクにかぎらず色々な制御関係を反転することも IoC と言います。
Robotlegs の Injector の機能だけを利用した IoC で「ユーザ名が変更されたら表示する」という機能を実装してみましょう。
反転されていない書き方
public class User
{
private var _printer:Printer = new Printer;
private var _name:String;
public function get name():String { return _name; }
public function set name(value:String):void
{
_name = value;
_printer.print("The user's name is " + name + ".");
}
}
var user:User = new User();
user.name = "Taro"; // => "The user's name is Taro."
一応表示されます。実装も短くてシンプルです。
しかし、これだと単なる User クラスが文字を表示する機能 Printer を知る必要がありますよね。
この依存関係はあまり良くないので、DI で切り離してみましょう。
制御の反転を行った書き方
public class User
{
public const nameChanged:Signal = new Signal;
private var _name:String;
public function get name():String { return _name; }
public function set name(value:String):void
{
_name = value;
nameChanged.dispatch();
}
}
public class PrintUsernameCommand
{
[Inject]
public var user:User;
private var _printer:Printer = new Printer;
public function execute():void
{
_printer.print("The user's name is " + user.name + ".");
}
}
// configuration
var user:User = new User;
injector.map(User).toValue(user);
// when user.name changed then print the name
user.nameChanged.add(function():void
{
// command instantiation
var command:PrintUsernameCommand = new PrintUsernameCommand();
injector.injectInto(command);
// command execution
command.execute();
});
user.name = "Taro"; // => "The user's name is Taro."
注) as3signals というライブラリを使っています http://qiita.com/tosik/items/2c1596abf3d43077fe6e
かなり複雑に見えるかもしれませんが、User クラスが Printer クラスを知らなくてよくなりました。
User と PrintUsernameCommand に分離し、User#name の変更時にその user インスタンスをインジェクとした PrintUsernameCommand の execute が実行されるようになっています。
ただ、依存分離が行えても Main 部分が雑だし記述量が多いのが面倒です。
そこで登場するのが Robotlegs です。(さっきのは Robotlegs の Injector しか使っていません)
Robotlegs による MVC の実例
先ほどの「ユーザ名が変更されたら表示する」を MVC で組み立てましょう。
今回は Printer を用いず TextField で名前を表示してみます。
Model
public class User
{
public const nameChanged:Signal = new Signal;
private var _name:String;
public function get name():String { return _name; }
public function set name(value:String):void
{
_name = value;
nameChanged.dispatch();
}
}
モデルは特に何も継承せず、普通のモデルクラスを書いてください。
View
public class UserView extends Sprite
{
private var _nameTextField:TextField;
public function UserView()
{
_nameTextField = new TextField;
addChild(_nameTextField);
}
public function changeName(name:String):void
{
_nameTextField.text = name;
}
}
ビューも Flash らしい普通の作り方で問題ありません。
一つ重要なのは UserView#changeName が public に実装されていることですね。
Mediator
public class UserViewMediator extends Mediator
{
[Inject]
public var view:UserView;
[Inject]
public var user:User;
public override function initialize():void
{
user.nameChanged.add(changeView);
}
public override function destroy():void
{
user.nameChanged.remove(changeView);
}
public function changeView():void
{
view.changeName(user.name);
}
}
これが User と UserView の仲介を行うメディエータです。
「ユーザの名前が変更された」というきっかけで「ビューにその名前を反映する」タスクを実行するだけのクラスです。
このクラスが Model-View の IoC において最も重要なものです。
Configuration
public class MainConfig implements IConfig
{
[Inject]
public var injector:IInjector;
[Inject]
public var mediatorMap:IMediatorMap;
[Inject]
public var contextView:ContextView;
public function configure():void
{
injector.map(User).asSingleton();
mediatorMap.map(UserView).toMeidator(UserViewMediator);
contextView.addChild(new UserView);
}
}
そしてこのクラスが、MVC の登場人物の依存関係のコンフィギュレーションを行うクラスです。
次のようなことを設定しています。
- User はシングルトンである
- UserView の対になるメディエータは UserViewMediator である
Application
public class Application extends Sprite
{
public function Apllication()
{
var context:Context = new Context()
.install(MVCSBundle)
.configure(MainConfig)
.configure(ContextView(this))
context.initialize();
}
}
この Application クラスは Flash のエントリポイント用のルート Sprite です。
Robotlegs の初期化のお決まりの書き方となります。(拡張を導入するときはこの辺りに手を入れる)
以上が Robotlegs の基本的な使い方でした。
動作するサンプルコード
中くらいのサイズのサンプルコードとして使えそうなコードを github に上げています。合わせてご覧ください。
https://github.com/tosik/zenith