2015年5月27日水曜日

コマンド実行型のコントローラ実装例

backbone.jsの影響を受けてから2年。
新しい技術を追い求めて色々と勉強してきて辿り着いた答えは

 今も昔もバグの無いソフトを作る事。

これに尽きる。

オブジェクト思考だとか関数型だとかトレンドに追随してきてコーディングスタイルの改変して実装している段階は使い物にならない。
すべては枯れた技術で実装される必要があるからだ。

師匠レベルと思っている人の業を見ると10年以上前のソースをそのまま使いまわせるんだから、そろそろ自分も落ち着こうと思っている。



まず第1段としてオレオレデザインパターンガイドのMVC部分から書いてみた。


Viewはどんな書き方しても大丈夫だから省略。


Modelクラス→Collectionクラスへとデータを集約していく。

複数のCollectionクラスから特定のデータを寄せ集めて作る人がFactory。
生成物はProductModel(コンポジットクラス)

これをController(ストラテジークラス)で被せる。

new Controller(Model);  // こんな具合

Controllerには関数テーブルもどきにシナリオが書けるように実装している。
こうすることでドキュメント{ユースケース図とシナリオ}と1対1で一致するようになって
テンプレートクラスの自動生成が可能になる期待ができるわけである。
もしこれが通信部分ならシーケンス図に該当する。

サブプロセス[箇条書きのシナリオを実装]
プロセス[サブプロセスのコンテナ]、コマンドを受けて該当するサブプロセスをピックアップして実行するだけ。
実行処理はサブプロセスワーカーという担当者で、処理するサブプロセスとパラメータを渡して後は頼んだという感じに任せる。


----Model部(Modelクラスがテーブル定義でCollectionクラスがレコード)----
class Model
{
    public int param1 { get; set; }
    public int param2 { get; set; }
}

class Collection : List
{
}

// Compisiteクラス()
class ProductModel
{
    Model model1;
    Model model2;
}

// FactoryクラスでProductModelを生成
class Factory
{
    Collection col1 = new Collection();
    Collection col2 = new Collection();

    public ProductModel Create()
    {
        return new ProductModel(col1[0], col2[1]); // 発展形はControllerクラスに被せて戻り値で返す
    }
}
----------------------------------------------------

----Controller部(backbone.jsではRouterに当たる部分)----


using System;
using System.Collections.Generic;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        //コントローラ生成
        var ctrl = new testController(); //今回はModelなし
        //試しに非同期Start・同期Executeの2種類実行
        ctrl.Start(Instruction.TEST1, null).Warker.Wait();
        ctrl.Execute(Instruction.TEST2, new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase) { { "foo", "bar"}, { "hello", "world" } });
        Console.ReadKey();
    }
}

/// <summary>
/// サンプル実装コントローラ
/// </summary>
public class testController
{
    private MainProc thread;
    public testController()
    {
        this.thread = new MainProc()
        {
            { Instruction.TEST1, new SubProc(test1, "TEST1") },
            { Instruction.TEST2, new SubProc()
                {
                    { StepID.Step1, new SubFunc(test21, "T2-1", StepID.Step2, StepID.FIN) },
                    { StepID.Step2, new SubFunc(test22, "T2-2", StepID.Step3, StepID.FIN) },
                    { StepID.Step3, new SubFunc(test23, "T2-3", StepID.Step4, StepID.FIN) },
                    { StepID.Step4, new SubFunc(test24, "T2-4", StepID.FIN, StepID.FIN) },
                }
            },
        };
        this.thread.Initialize();
    }

    public SubProcWarker Start(Instruction inst, Dictionary<string, string> arguments = null)
    {
        return this.thread.Start(inst, arguments);
    }

    public bool Execute(Instruction inst, Dictionary<string, string> arguments = null)
    {
        var proc = Start(inst, arguments);
        proc.Warker.Wait();
        return !proc.IsAbort;
    }

    bool test1()
    {
        Console.WriteLine("TEST1: OK");
        return true;
    }

    bool test21(SubProcWarker sender)
    {
        var value = sender.Arguments["Foo"];
        Console.WriteLine("TEST2-1: " + value);
        return true;
    }
    
    bool test22(SubProcWarker sender)
    {
        var value = sender.Arguments["hello"];
        Console.WriteLine("TEST2-2: " + value);
        return true;
    }
    bool test23(SubProcWarker sender)
    {
        var value = sender.ExeFunc.StateMessage;
        Console.WriteLine("TEST2-3: " + value);
        return true;
    }

    bool test24()
    {
        Console.WriteLine("TEST2-4: OK");
        return true;
    }
}

/// <summary>
/// サブプロセス用ステップ番号
/// </summary>
public enum StepID
{
    FIN = 0,
    Step1,
    Step2,
    Step3,
    Step4,
    Step5,
}
public class StepIDComparerIgnoreCase : IEqualityComparer<StepID>
{
    public bool Equals(StepID x, StepID y)
    {
        return x.Equals(y);
    }

    public int GetHashCode(StepID obj)
    {
        return (int)obj;
    }
}
/// <summary>
/// コマンド一覧
/// </summary>
public enum Instruction
{
    TEST1,
    TEST2,
}
public class InstructionComparerIgnoreCase : IEqualityComparer<Instruction>
{
    public bool Equals(Instruction x, Instruction y)
    {
        return x.Equals(y);
    }

    public int GetHashCode(Instruction obj)
    {
        return (int)obj;
    }
}


/// <summary>
/// ファンクション(1処理単位)
/// </summary>
public class SubFunc
{
    private IFunc function;
    private StepID TRUE;
    private StepID FALSE;
    public string StateMessage { get; private set; }
    private SubFunc FuncT;
    private SubFunc FuncF;
    public SubFunc(Func<bool> action, string stateMessage, StepID TRUE, StepID FALSE)
    {
        this.function = new FuncA(action);
        this.TRUE = TRUE;
        this.FALSE = FALSE;
    }
    public SubFunc(Func<SubProcWarker, bool> action, string stateMessage, StepID TRUE, StepID FALSE)
    {
        this.function = new FuncB(action);
        this.TRUE = TRUE;
        this.FALSE = FALSE;
        this.StateMessage = stateMessage;
    }
    public void Initialize(SubProc funcTable)
    {
        this.FuncT = (this.TRUE == StepID.FIN) ? null : funcTable[this.TRUE];
        this.FuncF = (this.FALSE == StepID.FIN) ? null : funcTable[this.FALSE];
    }
    public SubFunc Dispatch(SubProcWarker sender)
    {
        return this.function.Dispatch(sender) ? this.FuncT : this.FuncF;
    }

    #region "内部I/F"
    interface IFunc
    {
        bool Dispatch(SubProcWarker sender);
    }
    class FuncA : IFunc
    {
        private Func<bool> action;
        public FuncA(Func<bool> action)
        {
            this.action = action;
        }
        public bool Dispatch(SubProcWarker sender)
        {
            return this.action();
        }
    }
    class FuncB : IFunc
    {
        private Func<SubProcWarker, bool> action;
        public FuncB(Func<SubProcWarker, bool> action)
        {
            this.action = action;
        }
        public bool Dispatch(SubProcWarker sender)
        {
            return this.action(sender);
        }
    }
    #endregion
}

/// <summary>
/// ファンクションテーブル(1プロセス単位)
/// 本テーブル内で1アクションの処理が完結する
/// </summary>
public class SubProc : Dictionary<StepID, SubFunc>
{
    public SubProc()
        : base(new StepIDComparerIgnoreCase())
    {
    }
    public SubProc(Func<bool> action, string stateMessage)
        : base(new StepIDComparerIgnoreCase())
    {
        this[StepID.Step1] = new SubFunc(action, stateMessage, StepID.FIN, StepID.FIN);
    }
    public SubProc(Func<SubProcWarker, bool> action, string stateMessage)
        : base(new StepIDComparerIgnoreCase())
    {
        this[StepID.Step1] = new SubFunc(action, stateMessage, StepID.FIN, StepID.FIN);
    }
    /// <summary>
    /// 初期化処理
    /// </summary>
    public void Initialize()
    {
        foreach (var model in this.Values)
        {
            model.Initialize(this);
        }
    }

    public SubProcWarker Start(Dictionary<string, string> arguments = null)
    {
        return new SubProcWarker(this[StepID.Step1], arguments);
    }
}

/// <summary>
/// サブプロセス実行クラス
/// </summary>
public class SubProcWarker
{
    public Task Warker { get; private set; }
    public Dictionary<string, string> Arguments { get; private set; }
    public SubFunc ExeFunc;
    public bool IsAbort { get; set; }
    public bool IsPause { get; set; }
    public SubProcWarker(SubFunc func, Dictionary<string, string> arguments = null)
    {
        this.Arguments = arguments;
        this.ExeFunc = func;
        this.Warker = Task.Run((Action)MainLoop);
    }

    private void MainLoop()
    {
        while (null != this.ExeFunc)
        {
            if (this.IsAbort) break;
            if (!this.IsPause)
            {
                this.ExeFunc = this.ExeFunc.Dispatch(this);
            }
            System.Threading.Thread.Sleep(1);
        }
    }
}

/// <summary>
/// プロセス(コマンド一覧、複数のサブプロセスを抱える)
/// </summary>
public class MainProc : Dictionary<Instruction, SubProc>
{
    public MainProc()
        : base(new InstructionComparerIgnoreCase())
    {
    }
    public void Initialize()
    {
        foreach (var funcTable in this.Values)
        {
            funcTable.Initialize();
        }
    }

    public SubProcWarker Start(Instruction inst, Dictionary<string, string> arguments)
    {
        return this[inst].Start(arguments);
    }
}

コメントを投稿

Androider