DSL / Decision Table


DSL

意思決定表

一言要約

意思決定表は、条件のグループを表として表現することによって理解しやすくしてくれます。各列がある特定の組み合わせの条件による結果を示しています。

要約

仕組み

  • 意思決定表は、条件と結果の2つのセクションに分かれています。
    • 各条件の行は条件の状態を示します。
    • 単純な2つの値であるブール値の条件で行の各セルは trueかfalse になります。
    • 各結果の行がテーブルから1つの出力の値を表しています。
    • 各セルが同じ列の条件に一致した場合の値を表しています。
    • 1つの結果を必要とするだけでなく、うれしいことにより多くの結果を受け入れることができます。
  • 意思決定表の価値のある特性
    • すべての条件の並びが列で捉えられているかどうかをあなたが判断できるということ。
    • ユーザのために不足している並びを示します。
  • ファウラーのお勧め
    • 意思決定表セマンティックモデルとパーサを別々に組み立てることを勧めています。
  • 意思決定表の良いところ
    • とても簡単にフォローすることができ、確実に編集できるため、ドメインの専門家から情報をキャプチャすることに特に適しています。
    • 多くのドメインの専門家はスプレッドシートに慣れ親しんでいます。
    • スプレッドシートは、独自のプログラミング言語でデータの受信、編集、意思決定表のデータを遠隔のプログラムへ送信するようにプログラムすることができるExcelに似ています。

いつ使うのか

  • 意思決定表は、条件の相互作用のセットの結果をキャプチャするための非常に効果的な方法です。

例:注文のための手数料の計算( C #)

モデル

  • ここでのセマンティックモデルは意思決定表になります。
  • それぞれ3つの条件の値であるbooleanをサポートする、いくつかの数値と条件を扱うことができる意思決定表を作成します。
  • 入力と出力の型を指定するために C#のジェネリックを使用。
class DecisionTable <Tin, Tout>{
   readonly List<Condition<Tin>> conditions = new List<Condition<Tin>>();
   readonly List<Column<Tout>> columns = new List<Column<Tout>>();


  • テーブルは2種類の設定が必要となり、条件とカラムをそれぞれの独自のクラスを取得します。
class DecisionTable...
   public void AddCondition(string description, Condition<Tin>.TestType test) {
     conditions.Add(new Condition<Tin>(description, test));
   }
   public class Condition<T> {
     public delegate bool TestType(T input);
     public string description { get; private set; }
     public TestType Test { get; private set; }
     public Condition(string description, TestType test) {
       this.description = description;
       this.Test = test;
     }
   }


  • 例えば、これらの条件をこのコードのテーブルの条件を設定することができます。
  • 意思決定表のための入力タイプが注文です。
         var decisionTable = new DecisionTable<Order, FeeResult>();
         decisionTable.AddCondition("Premium Customer", o => o.Customer.IsPremium);
         decisionTable.AddCondition("Priority Order",   o => o.IsPriority);
         decisionTable.AddCondition("Priority Order",   o => o.IsInternational);


  • 出力は、出力データをラップするだけの特別なクラスです。
 class FeeResult {
   public int Fee { get; private set; }
   public bool shouldAlertRepresentative { get; private set; }
   public FeeResult(int fee, bool shouldAlertRepresentative) {
     Fee = fee;
     this.shouldAlertRepresentative = shouldAlertRepresentative;
   }


  • テーブルの設定の次の部分は、Columnの値を取得することです。
  • 再びカラムのためのクラスを使用します。
 class Column <Tresult> {
   public Tresult Result { get; private set; }
   public readonly ConditionBlock Conditions;
   public Column(ConditionBlock conditions, Tresult result) {
     this.Conditions = conditions;
     this.Result = result;
   


  • (省略)

パーサー

  • 表形式で、多くの場合の最も良いエディタの形式は、スプレッドシートです。
  • スプレッドシートからC#のプログラムにデータを取得する方法はたくさんあります。そして、私はここではそのことについては記述しません。
  • 私はテーブルのための簡単なインターフェイスで動作するパーサを作成します。
 interface ITable {
   string cell(int row, int col);
   int RowCount {get;}
   int ColumnCount {get;}
 }


  • デリミタ直接変換のつもりでテーブルをパースしますが、ストリームのデリミタで分けられたトークンの代わりに行や列を使用しています。
  • パーサーの基本的な構造は、入力としてITablesを取得して、出力として意思決定表を返すコマンドオブジェクトです。
class TableParser...
   private readonly DecisionTable<Order, FeeResult> result = new DecisionTable<Order, FeeResult>();
   private readonly ITable input;
   public TableParser(ITable input) {
     this.input = input;
   }
   public DecisionTable<Order, FeeResult> Run() {
     loadConditions();
     loadColumns();
     return result;
   }


  • 最初のステップは、条件をロードすることです。
class TableParser...
   private void loadConditions() {
     result.AddCondition("Premium Customer", (o) => o.Customer.IsPremium);
     result.AddCondition("Priority Order", (o) => o.IsPriority);
     result.AddCondition("International Order", (o) => o.IsInternational);
     checkConditionNames();
   }


  • ここでの潜在的な問題は、テーブルが条件を並べ替えたり、パーサーを更新しないで条件を変更してしまうかもしれないことです。
  • そこで条件の名前で簡単なチェックを行います。
    class TableParser...
       private void checkConditionNames() {
         for (int i = 0; i < result.ConditionNames.Count; i++)
           checkRowName(i, result.ConditionNames[i]);
       }
       private void checkRowName(int row, string name) {
         if (input.cell(row, 0) != name) throw new ArgumentException("wrong row name");
       }


  • テーブルの主な目的は、条件と私が次のステップで読む込む各列の結果を提供することです。
class TableParser...
   private void loadColumns() {
     for (int col = 1; col < input.ColumnCount; col++ ) {
       var conditions = new ConditionBlock(
         Bool3.parse(input.cell(0, col)),
         Bool3.parse(input.cell(1, col)),
         Bool3.parse(input.cell(2, col)));
       var consequences = new FeeResult(
         Int32.Parse(input.cell(3, col)),
         parseBoolean(input.cell(4, col))
         );
       result.AddColumn( conditions, consequences);
     }
   }


  • 入力テーブルから右側のセルを選んでいるだけでなく、適切な値に文字列をパースする必要があります。
class Bool3...
   public static Bool3 parse (string s) {
     if (s.ToUpper() == "Y") return T;
     if (s.ToUpper() == "N") return F;
     if (s.ToUpper() == "X") return X;
     throw new ArgumentException(
       String.Format("cannot turn <{0}> into Bool3", s));
   }
class TableParser...
   private bool parseBoolean(string arg) {
     if (arg.ToUpper() == "Y") return true;
     if (arg.ToUpper() == "N") return false;
     throw new ArgumentException(
         String.Format("unable to parse <{0}> as boolean", arg));
   }

ファウラーへのフィードバック

担当者のつぶやき


みんなの突っ込み

  • DecisionTable?DSL表現がCSVみたいなテキストだった場合も、ITableみたいなインタフェースで抽象化した方がいいってことなんでしょうかね。 -- 和智? 2010-11-27 (土) 16:47:37