DSL / Object Scoping


DSL

オブジェクトスコーピング

一言要約

グローバル変数を避けるため、DSLの関数は基底クラスに定義し、サブクラスで利用する。

要約

How it Works

  • 基底クラスにDSLの関数を定義することで、関数とデータのスコープをサブクラスに閉じられる。
  • イクスプレッションビルダにおいては普通に使われるテクニック。
  • Rubyのinstance_evalのように、継承を使わなくてもオブジェクトスコープが使える言語もある。

When to use it

  • 以下の理由から、常に利用を検討する価値がある。
    • グローバル関数を避けることができる。
    • パース中のデータを保存できる。
  • オブジェクト指向言語を使っている事が前提(継承を使うから)
  • DSLをサブクラスに書けない場合には注意が必要。
  • グローバル関数を使っても大丈夫なケースでは、オブジェクトスコーピングを使わなくても良い。
    • 単純にインスタンスを生成して返すだけの場合など

セキュリティの例

定義したいルール

   class MyZone : ZoneBuilder {
     protected override void doBuild() {
       Allow(
         Department("MF"),
         Until(2008, 10, 18));
       Refuse(Department("Finance"));
       Refuse(Department("Audit"));
       Allow(
         GradeAtLeast(Grade.Director),
         During(1100, 1500),
         Until(2008, 5, 1));
     }

抽象入場ルールクラス

 abstract class AdmissionRule {
   protected RuleElement body;
   protected AdmissionRule(RuleElement body) {
     this.body = body;
   }
   public abstract AdmissionRuleResult CanAdmit(Employee e);
 }
 enum AdmissionRuleResult {ADMIT, REFUSE, NO_OPINION};

※実装クラスとして許可ルールと拒否ルールができる。

ルールを使うzoneクラス

class Zone...
   private IList<AdmissionRule> rules = new List<AdmissionRule>();
   public void AddRule(AdmissionRule arg) {
     rules.Add(arg);
   }
   public bool WillAdmit(Employee e) {
     foreach (AdmissionRule rule in rules) {
       switch(rule.CanAdmit(e)) {
         case AdmissionRuleResult.ADMIT:
           return true;
         case AdmissionRuleResult.NO_OPINION:
           break;
         case AdmissionRuleResult.REFUSE:
           return false;
         default:
           throw new InvalidOperationException();
       }
     }
     return false;
   }

スコーピングのための基底クラス

class ZoneBuilder...
   private Zone zone;
   public ZoneBuilder Allow(params RuleElement[] rules) {
     AndExpr expr = new AndExpr(rules);
     zone.AddRule(new AllowRule(expr));
     return this;
   }

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

担当者のつぶやき

  • 後半の例はオブジェクトスコーピングというより、イクスプレッションビルダの説明ですね。Java版が欲しい・・・(自分で書けという話)
  • 「必ず継承が必要になる」というのは、多重継承ができないJavaだと厳しくないんでしょうか。その場合は、static import + thread local変数の組み合わせということになるのかな。

みんなの突っ込み