[DesignPatterns] Stateパターンでif文をなくす

Tags: ,

プログラミングテクニックのまとめ - プログラミング日記の「ifをポリモーフィズムによりなくせ」に相当するものをActionScriptで実践した。以下の例では分岐が高々2箇所なのでif文をなくす労力の方が無駄に思えるが、if文やswitch文の分岐箇所が増えるような場合ほど威力を発揮するはず(拡張が容易・メンテナンスが楽などの理由)。

a. if文が必要な場合

例えば線を描画したいとき、描画始点はGraphics.moveTo()、その他はGraphics.lineTo()を使う。そのため以下の例のようにforループでArrayデータの内容を読み込んで折れ線グラフを描画させるルーチン内ではif文を使う必要がある。

package {
    import flash.display.Sprite;
    import flash.display.Shape;

    public class Main extends Sprite{
        private var _drawer:Shape;
        private var _data:Array = [0,10,0,10,0,10,0,10,0,10];//Test data

        public function Main(){
            _drawer = new Shape();
            addChild(_drawer);
            draw();
        }

        private function draw():void{
            _drawer.graphics.lineStyle(1,0x0);
            for (var i:int=0; i<_data.length; i++){
                var x:Number = 20*i;
                var y:Number = 20*_data[i];

                if (i==0){
                    _drawer.graphics.moveTo(x,y);
                }
                else {
                    _drawer.graphics.lineTo(x,y);
                }
            }
        }
    }
}

この分岐処理はGraphics.moveTo()とGraphics.lineTo()という全く別のメソッドを「直接」使うから生じている。つまり、これらメソッドをコントロールする管理クラスを新たに作って、管理クラス経由で「間接」的に扱えば(delegateする、と言う)回避することが可能になるわけだ。具体的にはforループ内ではその管理クラスの単一メソッドのみを呼び出すようにする。

b. if文が不要な場合

以下の例では管理クラスLineDrawer.asを導入して描画させている。線の始点であろうがなかろうが常にLineDrawer.draw()というメソッドを使うので結果if文がなくなっている。

package {
    import flash.display.Sprite;
    import com.borealkiss.controls.LineDrawer;

    public class Main extends Sprite{
        private var _drawer:LineDrawer;
        private var _data:Array = [0,10,0,10,0,10,0,10,0,10];//Test data

        public function Main(){
            _drawer = new LineDrawer();
            addChild(_drawer);
            draw();
        }

        private function draw():void{
            _drawer.graphics.lineStyle(1,0x0);
            for (var i:int=0; i<_data.length; i++){
                var x:Number = 20*i;
                var y:Number = 20*_data[i];
                _drawer.draw(x,y);
            }
        }
    }
}

以下if文がなくなったカラクリ部分について。

b-1. LineDrawer.as (管理クラス)

Main.asから呼ばれるのはLineDrawer.draw()メソッドのみ。ポイントはLineDrawer.draw()内で実際に働く中身の入れ替えを行っているところ。最初にLineDrawer.draw()が呼ばれたときに実際に働くのがプロパティ”_moveToState”にセットされたクラス(MoveToState.as)、それ以降はプロパティ”_lineToState”にセットされた「別」クラス(LineToState.as)で、それぞれdraw()というメソッドを持っているがメソッドの内容は全く別物になっている(b-3, b-4参照)。そしてこの二つのクラスの受け皿に担当するプロパティ”_currentState”はこの両クラスを参照できるようにそれぞれの同一継承元であるクラス型(IState.as)で定義されている。これがポリモーフィズム(クラス型の多様性)。イメージとしてはクラス型を日本人型に設定しておき、東京人型クラスも大阪人型クラスも参照できるような状態。

package com.borealkiss.controls{
    import flash.display.Shape;

    public class LineDrawer extends Shape{
        private var _moveToState:IState;
        private var _lineToState:IState;
        private var _currentState:IState;

        public function LineDrawer(){
            _moveToState = new MoveToState(this);
            _lineToState = new LineToState(this);
            init();
        }

        public function draw(x:Number,y:Number):void{
            _currentState.draw(x,y);
            _currentState = _lineToState;
        }

        public function init():void{
            _currentState = _moveToState;
        }

    }
}

b-2. IState.as (インターフェイス)

LineDrawer.as(管理クラス)内で呼び出されて働かされるクラスMoveToState.asとLineToState.asが継承している親玉クラス。これがあるからポリモーフィズムが可能になる。重要なのはMoveToStateクラスとLineToStateクラスが共通して継承しているクラスが存在していることなので、インターフェイスを使わなくてもアブストラクト的なクラスを作成して代用してもよい(MoveToState extends XXX, LineToState extends XXX, のXXXに相当するクラス)。

package com.borealkiss.controls{
    public interface IState{
        function draw(x:Number,y:Number):void;
    }
}

b-3. MoveToState.as

折れ線グラフの始点描画時点でのみ呼ばれるクラス。MoveToState.draw()はGraphics.moveTo()を実際に行う。

package com.borealkiss.controls{
    public class MoveToState implements IState{
        protected var _drawer:LineDrawer;

        public function MoveToState(drawer:LineDrawer){
            _drawer = drawer;
        }

        public function draw(x:Number,y:Number):void{
            _drawer.graphics.moveTo(x,y);
        }
    }
}

b-4. LineToState.as

折れ線グラフの始点以降で呼ばれるクラス。LineToState.draw()はGraphics.lineTo()を実際に行う。MoveToStateクラスを継承しているので必然的にIStateインターフェースも継承している。

package com.borealkiss.controls{
    public class LineToState extends MoveToState{

        public function LineToState(drawer:LineDrawer){
            super(drawer);
        }

        override public function draw(x:Number,y:Number):void{
            _drawer.graphics.lineTo(x,y);
        }
    }
}

[Books]ActionScript 3.0 Design Patterns

Tags: , ,

ActionScript 3.0 Design Patterns (Adobe Developer Library)

プログラムの設計思想と具体的な王道設計パターンについて豊富に例を挙げつつ説明してる本。内容自体はどのプログラミング言語(OOP)にも共通なのだけど、わざわざActionScriptを題材にしている点が貴重。ActionScriptに馴染みのある人は親近感を持ちやすいのではないかと思う。章ごとに各デザインパターンの性質、最低限の骨組み、簡単な例、少し複雑な例と書かれていて段階的に読めるからわかりやすかった。さらに章内容が独立しているので、例えばStrategy > Composite > Observer > Model-View-Controllerパターンと必要な部分だけを読むことで短期間でwebアプリケーション制作に役立つ設計技術が習得できそうな感じがする。個人的にはデザインパターン云々よりもそれ以前のOOPに関する知識・思想の理解が深まったのが収穫。タイトルのActionScriptとDesign Patternはおまけかな。

良書だと思うけど、難点も結構有り。デザインパターンの説明をする際に、日常生活の例を挙げて説明することが多いのだが、これが非常にわかりにくい(日本人的でないというのもあるかも)。まずは必要最低限のプログラムの骨格を提示するところから始まる方がよかったのではないかと思う。さらにプログラム全体のフローチャート図がたくさん挙げられるが、図の説明が全くないので理解不能。図は非常にわかりやすそうな絵柄をしているだけに残念で仕方ない。最後に、ソースの誤字脱字が半端なく多い(10-20どころではない)。typoというレベルではなく、引数が書いてなかったりさらには一行きれいさっぱり書かれていなかったりするものもありちょっとびびる。

こういう弱点もふまえてオススメの読み方としては、

  1. 第一章のOOP基本概念を読む
  2. 各デザインパターン章の「Minimalist example」以降を読む(それ以前は前置きが長い)
  3. 「Minimalist Example」で理解できなければそれ以前の概念部分を読む
  4. 各章最後部はプログラムが複雑になっていくだけなので、自分が必要だと思ったところまでで適当に切り上げる(逆に言うと説明不足ということはないほど例題が豊富)

追記 2008/07/19

最終章「13. Symmetric Proxy Pattern」は未読。

追記 2008/07/20

完読。

追記 2008/08/08

レーダーチャートによる評価レビューはこちら

[DesignPatterns]疑似privateコンストラクタで勇者を唯一作成

Tags: ,

Design PatternsのSingletonパターンついてのメモ。例えば以下のような勇者クラス(Yusha.as)を作成したとする。

package singleton{
    public class Yusha{
        public function sayHello():void{
            trace("僕が勇者だ");
        }
    }
}

RPGにおいて勇者は一般に物語中に一人しか存在しない。従って以下のように複数勇者が作成できるアプリケーション(Main.as)はRPG的に問題がある。

package {
    import flash.display.Sprite;
    import singleton.Yusha;

    public class Main extends Sprite{
        public function Main(){
            var y1:Yusha = new Yusha();
            var y2:Yusha = new Yusha();
            var y3:Yusha = new Yusha();

            y1.sayHello();//僕が勇者だ
            y2.sayHello();//僕が勇者だ
            y3.sayHello();//僕が勇者だ
        }
    }
}

そこでSingletonパターンを利用する。例えばCreating Mashups with Adobe Flex and AIRでは以下のような方法でインスタンスを唯一作成する方法が紹介されている。

package singleton{
    public class Yusha{

        private static var _instance:Yusha;

        public static function getInstance():Yusha{
            if (Yusha._instance == null){
                Yusha._instance = new Yusha();
            }
            return Yusha._instance;
        }
    }
}

この方法だとgetInstance()メソッドを使う限り勇者は複数作成されないが、以下のように直接Yushaクラスを作成された時に対処できなくなる。

//In Main.as
var y:Yusha = new Yusha();

Yushaクラスのコンストラクタをprivateにできれば一件落着だけど、ActionScript 3.0ではコンストラクタをprivateにできない。そこで同パッケージ内にDummyクラスを用意、それをYushaクラスの引数としてYushaクラスのコンストラクタにパッケージ外部からアクセスできないようにする(疑似private化)。

//Yusha.as
package singleton{
    public class Yusha{

        private static var _instance:Yusha;

        public function Yusha(d:Dummy){
        }

        public static function getInstance():Yusha{
            if (Yusha._instance == null){
                Yusha._instance = new Yusha(new Dummy());
            }
            return Yusha._instance;
        }

        public function sayHello():void{
            trace("僕が勇者だ");
        }
    }
}
//Dummy.as
package singleton{
    internal class Dummy{
    }
}

これによりYushaクラスは作成時にDummyクラスを引数とし、さらにDummyクラスはsingletonパッケージ外部からアクセス不能なので、MainクラスからのYushaクラスの呼び出しはgetInstance()メソッドからのみ可能となる。

//In Main.as
var y1:Yusha = new Yusha();//Error
var y2:Yusha = new Yusha(new Dummy());//Error
var y3:Yusha = Yusha.getInstance();//OK

これにより勇者は唯一無二作成されることになりました。めでたしめでたし。

[関連記事]
Tanablog: 複数版 Singleton パターン

ActionScript 3.0 Design Patterns (Adobe Developer Library)

追記:2008/07/11

Dummyクラスはsingletonパッケージ内であればアクセス可能なので、記事タイトルの「疑似private」は「疑似internal」とした方が良いかもしれない。AS3で1ファイルに複数のクラスを定義する方法 - てっく煮ブログのように、DummyクラスをYushaクラスファイル内部に定義することで、Dummyクラスへのアクセスをprivate化することもできる。