kagamihogeの日記

kagamihogeの日記です。

Cairngorm さわってみた

Home Project for Open @ Adobe / Home / Projects とは Flex のクライアント側のフレームワーク

詳細は Cairngorm Frameworkとは? - @IT の記事がかなり参考になる。ただし、古いバージョン前提で書かれているのでクラス名などは読み替えが必要。実際に使用してみる方法や、各クラスの役割については (33) Cairngorm 2.1 シリーズの目次 - hirossy javaとFlex2と。 にまとめられている。

今回は上記 2 つの URL を参考に、お約束の足し算(と掛け算)サンプルを作ってみた。オマケで Cairngorm + S2BlazeDS もやっている。

Cairngorm 2.2.1 を入れる

上記 URL から Cairngorm 2.2.1 Binary を DL してきて解凍、bin/Cairngorm.swc をライブラリパスの設定してあるディレクトリにコピーする。EclipseFlex 開発プロジェクト作った場合は libs ディレクトリにコピペすれば準備完了。

View(MXML)をつくる

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:view="viewhelper.*"
    xmlns:controller="controller.*"
    layout="vertical" xmlns:local="*">
    <controller:CalcController id="calcController"/>
    <view:CalcViewHelper id="calcViewHelper"/>
    <local:CalculateServiceLocator/>

    <mx:HBox>
        <mx:TextInput id="arg1_txt"/>
        <mx:TextInput id="arg2_txt"/>
        <mx:Text id="ans_text" text="{calcViewHelper.model.dto.sum}"/>
        <mx:Button label="add" click="calcViewHelper.clickAdd(event)"/>
        <mx:Button label="multiply" click="calcViewHelper.clickMultiply(event)"/>
    </mx:HBox>
</mx:Application>

いっこ前のエントリで作ってた足し算 mxml に少し手を入れただけ。CalcViewHelper の宣言はともかく CalcController, CalculateServiceLocator の宣言がおまじないじみてキモいけど、各クラスのインスタンス初期化のために必要。

ViewHelper をつくる

package viewhelper
{
    import com.adobe.cairngorm.control.CairngormEventDispatcher;
    import com.adobe.cairngorm.view.ViewHelper;
    
    import event.AddEvent;
    import event.MultiplyEvent;
    
    import flash.events.MouseEvent;
    
    import flex.add.dto.AddDto;
    
    import model.CalcModelLocator;

    public class CalcViewHelper extends ViewHelper
    {
        [Bindable]
        public var model :CalcModelLocator = CalcModelLocator.getInstance();
        
        public function CalcViewHelper()
        {
            super();
        }

        public function clickAdd(event:MouseEvent):void {
            var dto:AddDto = new AddDto();
            dto.arg1 = int(this.view.arg1_txt.text);
            dto.arg2 = int(this.view.arg2_txt.text);
            CairngormEventDispatcher.getInstance().dispatchEvent(new AddEvent(dto));
        }
        
        public function clickMultiply(event:MouseEvent):void {
            var dto:AddDto = new AddDto();
            dto.arg1 = int(this.view.arg1_txt.text);
            dto.arg2 = int(this.view.arg2_txt.text);
            CairngormEventDispatcher.getInstance().dispatchEvent(new MultiplyEvent(dto));
        }
    }
}

画面の入力値から ValueObject を作成してイベントを発生させることによって Contoller へ処理を渡す。ValueObject は 前回のエントリ で使用した DTO を流用しているため、ちとネーミングが実態にあってない。

Event をつくる

package event
{
    import com.adobe.cairngorm.control.CairngormEvent;
    
    import controller.CalcController;
    
    import flex.add.dto.AddDto;

    public class AddEvent extends CairngormEvent
    {
        public var dto:AddDto;
        
        public function AddEvent(dto:AddDto, bubbles:Boolean=false, cancelable:Boolean=false)
        {
            super(CalcController.EVENT_ADD, bubbles, cancelable);
            this.dto = dto;
        }
    }
}
package event
{
    import com.adobe.cairngorm.control.CairngormEvent;
    
    import controller.CalcController;
    
    import flex.add.dto.AddDto;

    public class MultiplyEvent extends CairngormEvent
    {
        public var dto:AddDto;
        
        public function MultiplyEvent(dto:AddDto, bubbles:Boolean=false, cancelable:Boolean=false)
        {
            super(CalcController.EVENT_MULTIPLY, bubbles, cancelable);
            this.dto = dto;            
        }
    }
}

この辺は機械的な作業。ぶっちゃけめんどい。

Controller をつくる

package controller
{
    import com.adobe.cairngorm.control.FrontController;
    
    import command.AddCommand;
    import command.MultiplyCommand;

    public class CalcController extends FrontController
    {
        public static const EVENT_ADD : String = "event_add";
        public static const EVENT_MULTIPLY : String = "event_multiply";
        
        public function CalcController()
        {
            super();
            addCommand(CalcController.EVENT_ADD, AddCommand);
            addCommand(CalcController.EVENT_MULTIPLY, MultiplyCommand);
        }
        
    }
}

ここもめんどい設定系なコード。

Command をつくる

package command
{
    import businessdelegate.AddDelegate;
    
    import com.adobe.cairngorm.commands.ICommand;
    import com.adobe.cairngorm.control.CairngormEvent;
    
    import event.AddEvent;
    
    import flex.add.dto.AddDto;
    
    import model.CalcModelLocator;
    
    import mx.controls.Alert;
    import mx.rpc.IResponder;

    public class AddCommand implements ICommand, IResponder
    {
        public function AddCommand()
        {
        }

        public function execute(event:CairngormEvent):void
        {
            var addEvent :AddEvent = event as AddEvent;
            var logic:AddDelegate = new AddDelegate(this);
            logic.add(addEvent.dto);
        }
        
        public function result(data:Object):void{
            var dto:AddDto = data.result as AddDto;
            CalcModelLocator.getInstance().dto.sum = dto.sum;    
        }
        
        public function fault(info:Object):void{
            Alert.show("fault", "fault");
        }
    }
}
package command
{
    import com.adobe.cairngorm.commands.ICommand;
    import com.adobe.cairngorm.control.CairngormEvent;
    
    import event.MultiplyEvent;
    
    import model.CalcModelLocator;

    public class MultiplyCommand implements ICommand
    {
        public function MultiplyCommand()
        {
        }

        public function execute(event:CairngormEvent):void
        {
            var multipyEvent :MultiplyEvent = event as MultiplyEvent;
            CalcModelLocator.getInstance().dto.sum = multipyEvent.dto.arg1 * multipyEvent.dto.arg2;
        }
        
    }
}

AddCommand は 前回のエントリ で作成した S2BlazeDS のサンプルと通信して足し算を行う。MultiplyCommand はフツーに掛け算する。

ModelLocator をつくる

package model
{
    import com.adobe.cairngorm.CairngormError;
    import com.adobe.cairngorm.CairngormMessageCodes;
    import com.adobe.cairngorm.model.IModelLocator;
    
    import flex.add.dto.AddDto;

    [Bindable]
    public class CalcModelLocator implements IModelLocator
    {
        private static var instance:CalcModelLocator;
        
        public var dto:AddDto=new AddDto();
        
        public function CalcModelLocator()
        {
          if ( instance != null )
          {
             throw new CairngormError(
                CairngormMessageCodes.SINGLETON_EXCEPTION, "AddModelLocator" );
          }
           
          instance = this;
        }
        
        public static function getInstance() : CalcModelLocator 
        {
            if ( instance == null )
                instance = new CalcModelLocator();
                
            return instance;
        }
    }
}

Singleton にするためのコードがとてもうざい。

BusinessDelegate をつくる

package businessdelegate
{
    import com.adobe.cairngorm.business.ServiceLocator;
    
    import flex.add.dto.AddDto;
    
    import mx.rpc.AbstractService;
    import mx.rpc.AsyncToken;
    import mx.rpc.IResponder;
    
    public class AddDelegate
    {
        private var service:AbstractService;
        private var responder : IResponder;
        public function AddDelegate(responder:IResponder)
        {
            service = ServiceLocator.getInstance().getRemoteObject("calcSrv"); 
            this.responder = responder;
        }
        
        public function add(dto:AddDto):void {
            var token : AsyncToken =service.calculate(dto);
            token.resultHandler = responder.result;
            token.faultHandler = responder.fault;
        }
    }
}

RemoteObject でサーバ側と通信を行う。

サービスを定義

<?xml version="1.0" encoding="utf-8"?>
<cairngorm:ServiceLocator xmlns="*" xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:cairngorm="com.adobe.cairngorm.business.*">
    
    <mx:RemoteObject id="calcSrv" destination="addService"
        showBusyCursor="true" 
        result="event.token.resultHandler( event );"
        fault="event.token.faultHandler( event );">    
    </mx:RemoteObject>
    
</cairngorm:ServiceLocator>

addService については 前回のエントリ を参照。


コードの断片ばかりになってしまった……まぁ Cairngorm (+ S2BlazeDS)のサンプルってことで。どういう思想でこういうクラス構造になるの? ってところは @IT の記事読んだ方がハヤイので解説はなし。

Cairngorm 雑感

「うざいコードをたくさん書かにゃならんフレームワーク」という印象だなぁ……個人的には yui-frameworks のような薄いフレームワークの上にモリモリ書いていくやり方で十分かなぁ、という気がしないでもない。

なんかやたらとクラスやインタフェースの継承を要求してきたり、Controller とか Event とかの定型的なクラス作成を強いてきたりとメンドウ感が強い。mxml と画面周りいじる ViewHelper クラスとデータ通信/バインディング用の DTO クラスとロジック書く ActionScript クラス(Command か BusinessDelegate あたりに相当?)で充分、ってケース多いんでないかね? 大規模構成だとまた印象変わってきそうだけど。

でまぁ、Contoller とか Event とかの定型的なクラスを自動生成するツールがないとイライラすると思う。クラス数も爆発するし。Seasar 関連のプロダクトみたいに定型的でメンドウなクラスは作らんでも済むようになれば気持ちよく作れそうなんだけどなぁ。yui-frameworks の CoC とメタによるバインディングが出来るようにならないかしらん。

とはいえ Cairngorm の思想はそれなりに興味深い。どこのクラスにどんな処理書くかの縛りをかけられる点がね。いろんな人が開発に携わるとオレオレアーキテクチャがはびこっちゃうからなぁ。俺もその元凶の一人なんだけどw だったら多少重厚長大フレームワークであっても、皆が従うべき指針として使う、となればメリットはあるかな、と。

まぁフレームワークってのは大なり小なりそういうもんだけど。ひとつの指針として(J2EE パターンを参考に)こういうやり方もあるぜ、って示してるところは設計考える際には参考になると思う。