[AS3] Commandライブラリを作って非同期処理をする 1

2010/12/1

こんにちは。きんくまです。

今回はCommandクラスを作ってみようです。
Commandクラスといえば、Progressionやfladdictさんのcommandsライブラリなどが有名かと思いますが、今回はそれを自作してみようと思います。

と、思い立ってこの間やってみたところ意外と1時間ぐらいでできちゃって、キャンセル機能などをつけなければ、Serial, Parallelなんかもすぐに実装できました。
すでにお手本となるものがあると、早いもんですね。
基本的に何もみずにやってみたのですが、最終物をfladdictさんのやつと比べてみたらほとんど同じもんができてました。自分はSerial,Parallelに入れるのをCommandにしていたので、fladdictさんの真似をしてICommandにしときました。

Commandってなによ?

ところでこのCommandってなんでしょう?この間のf-siteでしっぽさんが講義してくれたデザインパターンの1つみたいです。
自分はデザパタはかじっただけなんでなんともいえないのですが、1言でいえば「命令をクラスとしてまとめたもの」ということになるみたいです。

例えばこんなコマンドクラスを作って

class HelloCommand {
	public function execute():void{
		trace("Hello command !");
	}
}
var cmd:HelloCommand  = new HelloCommand();
cmd.execute();

という感じに、インスタンス化してexecuteを呼び出すと命令が実行されるという仕組みですね。
ひとつひとつのCommandは1機能に特化したものの方が、あとから応用がきくみたいです。

Interfaceを定義

はじめにCommand用のInterfaceを定義します。
っていうか、私は最近までInterfaceって何なのよ?それおいしいの?っていう感じだったんだけど、なんとなく良さがわかってきました。

少し脱線します。
この記事をまず見ていただくといいです。
>> スパゲッティはオブジェクト指向の夢を見るか

オブジェクト指向を考えたときに、なるべく他のクラスと疎結合になるようにする。ということだそうです。
良いコードを書くためにはコードをかかないという話があるそうです。
つまり余計な情報はなるべく省くということだと思います。
本当にコードに必要な情報以外のものが入っていると、そこが後からいろいろと悪さをしてきて、他からの影響をイチイチ考慮しないといけなくなることがあります。
だから、なるべく互いの情報は知らないように書いた方がいいみたいです。

例)

class BaseA {
	public function BaseA() {
		
	}
	
	public function foo():void{
		trace("foo");
	}
	
	public function foofoo():void{
		trace("foofoo");
	}
}

class A1 extends BaseA{
	public function A1() {
		
	}
	public function bar():void{
		trace("bar");
	}
}

class A2 extends BaseA{
	public function A2() {
		
	}
	public function baz():void{
		trace("baz");
	}
}

class B {
	private var _ar:Array;
	public function B() {
		_ar = [];
	}
	public function add(a:BaseA):void{
		_ar.push(a);
	}
	public function execute():void{
		var a:BaseA;
		for (var i:int = 0; i < _ar.length; i++) {
			a = _ar[i];
			a.foo();
		}
	}
}

class Main {
	public function Main() {
		var b:B = new B();
		b.add(new A1());
		b.add(new A2());
		b.execute();
	}
}

BaseAというクラスがあって2つのメソッドがあります。
BaseAを拡張したA1, A2というクラスを作ってそれぞれに別のメソッドを追加しました。
BというクラスはA1,A2をいれておいて、executeをするとそれを実行するクラスです。
最後はBをまたさらに外から使うメインクラスMainです。
ちなみに、これ、少しいじったParallelコマンドなんです。これを作るときにまた説明します。

クラスBのexecuteメソッドをみると配列の中の変数をとりだしてBaseA型としてキャストしています。
ここで仮にexecuteコマンド内ではBaseAの中のfoo以外のメソッドは使わないとします。
そのとき、BaseAにはfooの他にfoofooというメソッドもあるんですが、Bにとってはこのメソッドの情報は全く必要がありません。

さきほどオブジェクト指向的には疎結合、つまり余計な情報はなるべく知らない方が、のちのちになったときいいよ!ということを書きました。
Bにとってはfoofooなんて情報は知りたくもないんです。
そこでInterfaceの出番です。

fooというメソッドを定義したInterface IFooを作ります。
それでこれを使ってさきほどのクラスたちをかきなおしてやります。

public interface IFoo
{
	function foo():void;
}

class BaseA {
	public function BaseA() {
		
	}
	
	public function foofoo():void{
		trace("foofoo");
	}
}

class A1 extends BaseA implements IFoo{
	public function A1() {
		
	}
	public function foo():void
	{
		trace("A1 foo");
	}
	public function bar():void{
		trace("bar");
	}
}

class A2 extends BaseA implements IFoo{
	public function A2() {
		
	}
	public function foo():void
	{
		trace("A2 foo");
	}
	public function baz():void{
		trace("baz");
	}
}

class B {
	private var _ar:Array;
	public function B() {
		_ar = [];
	}
	public function add(a:IFoo):void{
		_ar.push(a);
	}
	public function execute():void{
		var a:IFoo;
		for (var i:int = 0; i < _ar.length; i++) {
			a = _ar[i];
			a.foo();
		}
	}
}

class Main {
	public function Main() {
		var b:B = new B();
		b.add(new A1());
		b.add(new A2());
		b.execute();
	}
}

すると、クラスBはfooの情報以外を知ることがなくなりました。
また、A1,A2はIFooを実装しているよ(=メソッドfooが中に書いてありますよ)という情報を外部に示していて、かつA1とA2のfooはそれぞれ独自の中身を書くことができました。

つまり、疎結合をしつつ、それぞれfooを実装したクラスは独自の実装をする柔軟さをもつことができたのです。Interfaceってスゴイね。っていう感じです。

もちろん、一番初めに書いたみたいにfooをBaseAにもたせてA1とA2がfooを実行したときは、全く同じ動作をしたい場合もあるかと思います。その場合はBaseAにもたせてあげればいいと思います。
Interfaceという仕組みを利用すると、より柔軟なクラス設計をすることができるようになるよ。ということがいいたかっただけです。

長くなっちゃったんで今回はここまでにします。

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る