フレームワークからの呼び出し

フレームワーク的に処理を行いたい場合に、途中で非同期処理が挟まるときの対処方法を考えてみた。
例えば、次のコード

 public inteface IValidate{
   function validate():ValidateError{
   }
 }
 public class CloseSequence{
   public var validator:IValidate;
   public function doValidate():void{
      var error:ValidateError = validator.validate();
      if(error!=null){
         Alert.show(error.getMessage());
      }else{
         doViewClose();
      }
   }
   public function doViewClose():void{
      //画面を閉じる
   }
 }

があった場合、IValidateの実装クラスとして

  public class ValiateImp implements IValidate{
      public function validate():ValidateError{
         //UIの入力チェック
         return null;
      }
  }

というクラスであれば、期待通りに動く。しかしながらユーザーの入力により処理を変えるという非同期処理が仕様が追加になった場合

  public class UserValiateImp implements IValidate{
      public function validate():ValidateError{
         //ユーザーに確認を求める
         Alert.show("OK?","チェック",Alert.OK|Alert.Cancel);
     //UIのエラーチェック
         return null;
      }
  }

のように、Confirmなどを出してその結果でValidateErrorの値を変える様な場合、Alertを出した瞬間にreturn null;まで動いてしまうため、アラートが出ているのに元の画面が閉じる、という現象になる。
解決方法として3つ考えてみた。

  1. CloseSequenceクラスを拡張して、Confirmを出す処理をCloseSequenceに入れてしまう。
  2. アラートを扱うIConfirmValidateインターフェースと、それを扱うCloseSequece拡張クラスを作る
  3. validateメソッドの引数に次に実行したいSequenceのメソッドを渡す。

1の場合、業務仕様の入ったCloseSequenceの派生クラスを作成する必要があるが、イベント処理はSequenceに任せることが出来る。

 public class ConfirmCloseSequence extends CloseSequence{
    public function doValidate():void{
   //アラート
      Alert.show("OK?","チェック",Alert.OK|Alert.Cancel,doAfterConfirm);
   }
   public function doAfterConfirm():void{
       super.doValidate();
   }
 }

2の場合、作った枠組みを再利用できる可能性はあるが、1に比べて自由度が低い。

 public interface IConfirmValidate extends IValidate{
   function getConfirmText():String;
   function getConfirmTitle():String;
 }
 public class ConfirmCloseSequence extends CloseSequence{
    
    public function doValidate():void{
   //アラート
      var text:String = IConfirmValidate(validator).getConfirmText();
      var title:String = IConfirmValidate(validator).getConfirmTitle();
      Alert.show(text,title,Alert.OK|Alert.Cancel,doAfterConfirm);
   }
   public function doAfterConfirm():void{
       super.doValidate();
   }
 }

実装クラスは、getConfirmText,getConfirmTitleでそれぞれ「OK?」と「チェック」を返す。

3の場合はこうなる。

 public inteface IValidate{
   function validate(gotoNext:Function):void{
   }
 }
 public class CloseSequence{
   public var validator:IValidate;
   public function doValidate():void{
      var error:ValidateError = validator.validate(doAfterValidate);
   }
   public function doAfterValidate(error:ValidateError):void{
     if(error!=null){
         Alert.show(error.getMessage());
      }else{
         doViewClose();
      }
   }
   public function doViewClose():void{
      //画面を閉じる
   }
 } 

このフレームワーク仕様に対して、呼ばれる側はこう実装する。

  public class ValiateImp implements IValidate{
      public function validate(gotoNext:Fuction):void{
         var error:ValidateError;
        //UIのエラーチェック
        gotoNext(error);
      }
  }
 
  public class UserValiateImp implements IValidate{
      public function validate(gotoNext:Fuction):void{
         //ユーザーに確認を求める
         Alert.show("OK?","チェック",Alert.OK|Alert.Cancel,,function(e){
             if(e.detail== Alert.OK){
                 var error:ValidateError;
             //UIのエラーチェック
                 gotoNext(error);
             }
         });
      }
  }

IValidateでイベントの実装をする必要があるが、Validate側がコールバックタイミングを決められるため、柔軟に対応できる。
呼ぶ側呼ばれる側で、ちょうどキャッチボールのような形になる。
3の仕組みだと途中で非同期処理が挟まっても対応可能。
ただし「validateの引数のFunction」の引数に対してコンパイルチェックをかけることが出来ないという問題はある。
なかなか悩ましい。