DSL Book(by Martin Fowler) 翻訳ページ

Context Variable

原文はこちら: http://martinfowler.com/dslwip/ContextVariable.html
原文、翻訳共に作成中です。

Context Variable(コンテキスト変数)

パース過程で必要となる変数を保持する

複数のアイテムから構成されるリストをパースしているとします。それぞれのアイテム中から何らかのデータをキャプチャしているとしましょう。アイテム中のデータそれぞれは独立してキャプチャできるとして、今キャプチャしているデータを含むアイテム自体の情報も知っている必要があります。

Context Variable(文脈変数)は現在処理中のアイテムを変数に保持し、次のアイテムに進んだら順次再割り当てをすることで、この目的を達成します。

動作原理

パース処理中、currentItemのような変数を用意し、次のアイテムに移るごとに定期的に更新しているとしたら、それはContext Variableと呼ぶことができます。

Context VariableはSemantic Modelオブジェクトでも、何らかのビルダーでも構わないでしょう。Semantic Modelはより直接的ですが、使えるのはパース処理中にSemantic Modelの全プロパティが変更可能である時に限ります。そうでない場合は、Construction Builderのように、何らかのビルダーを使って情報を集め、最後にSemantic Modelを作り上げるのが良いでしょう。

いつ使うか

パース処理中に何らかのコンテキストを保持する必要性が生じることは良くあり、Context Variableはそのような場合の明確な候補です。Context Variableは簡単に導入できます。

しかし、Context Variableが問題となる場合があります。それは、多用しすぎてしまう場合です。Context Variableは元来、可変な状態なので、きっちり追跡しておかないとバグの温床になり得ます。というのも、Context Variableは然るべき時に更新するのを忘れやすいので、そデバッグがとても難しくなることがあります。通常の防衛策はContext Variableを必要とするパース処理を小さく保つようににパース処理を構成することです。私自身はContext Variable自体が邪悪なものだとは思っていませんが、それでもできるだけContext Variableが必要とならないテクニックを使うことを好みます(この本の中で、至る所で私のこの考えを目撃することになるでしょう)。

例:Iniファイルの読み込み(C#)

ここでは、Context Variableがどういうものか示すために、至極簡単な例であるiniファイルのパース処理を取りげることにします。iniファイルフォーマットは例としては良い素材です。Windowsではより改善されたレジストリがあります。なのでiniファイルは古めかしいものですが、それでも幾つかのプロパティを含むシンプルなリストを扱うには可読性が高く、軽いフォーマットです。代替としてのXMLやYAMLはより複雑な構造を扱えます。それでも必要としているものがシンプルなiniファイルで十分であるような場合、iniファイルは今でも合理的な選択肢です。

例に使うのは、プロジェクトのコードが幾つかあり、各プロジェクトに幾つかのプロパティが含まれているiniファイルを取り上げます。

[intro]
name = Introduction
lead = Martin

[type-transmog]
name = Type Transmogrification
lead=Neal

#line comment

[lang]    #group comment
name = Language Background Advice
lead = Revecca # item comment

iniファイルのフォーマットには標準がありませんが、基本となる要素はプロパティの一群で、それぞれ各セクションに割り当てられています。このiniファイルでは各セクションはプロジェクトのコードです。

このiniファイルのSemantic Modelは取るに足らないものです。

class Project...
    class Project {
        public string Code {get; set;}
        public string Name {get; set;}
        public srting Lead {get; set;}
    
iniファイルフォーマットはDelimiter Directed Translationを使って簡単に読み込むことができます。パーサの基本的な構造はスクリプト(訳注:ここではiniファイルのことかと)を行分割し、各行をパースしていくという通常のアプローチです。

class ProjectParser
	 private TextReader input;
	 private List<Project> result = new List<Project>();
	
	 public ProjectParser(TextReaer input) {
	   	this.input = input;
	 }
	 public List<Project> Run(){
		  string line;
		  while((line = input.ReadLine()) != null){
			    parseLine(line);
		  }
		  return result;
	 }

最初の節では各行のパーサは空白とコメントを処理します。

class ProjecrParser...
	private void parseLine(string s) {
		var line = removeComments(s);
		if (isBlank(line)) return;
		else if (isSection(line)) parseSection(line);
		else if (isProperty(line)) parseProperty(line);
		else throw new ArgumentException("Unable to parse: + line")
	}
	private strin removeComments(string s){
		return s.Split('#')[0];
	}
	private bool isBlank(string line){
		return Regex.IsMatch(line , @"^\s*$");
	}

Context VariableであるcurrentProject変数はその時点で割り当てられているセクションを保持するために使います。

class ProjectParser...
   private bool isSection(string line){
	    return Regex.IsMatch(line @"^\s\[");
   }
   private void parseSection(sting line) {
	    var code = new RegEx(@"\[(.*)\]").Match(line).Groups[1].value;
	    currentProject = new Project {Code = code};
	    result.Add(currentProject);
   }
   private Project currentProject;

そして、Context Variable(currentPojectメンバ)をプロパティをパースするときに使います。
class ProjectParser...
    private bool isProperty(string line){
        return Regex.IsMatch(line, @"=")
    }
    private void parseProperty(string line) {
        var tokens = extractPropertyTokens(line);
        setProjectProperty(tokens[0], tokens[1]);
    }
    private sting[] extracePropertyTokens(string line){
        char[] sep = {'='};
        var tokens = line.Split(sep,2);
        if (tokens.Lengs < 2) throw new ArgumentException("unable to split");
        for (var i = 0; i < tokens.Length; i++) tokens[i] = tokens[i].Trim();
        return tokens;
    }
    private void setProjectProperty(string name, string value){
        var proj = typeof(Project);
        var prop = proj.GetProperty(capitalize(name));
        if (prop == null) throw new ArgumentException("Unable to find property: " + name);
        prop.SetValue(currentProject, value, null);
    }
    private string capitalize(string s) {
        return s.Substring(0,1).ToUpper() + s.SubString(1).ToLower();
    }
    
リフレクションを使えばコードはより複雑になりますが、Semantic Modelにプロパティを追加した時にパーサのコードを変更しなくても良くなります。

名前:
コメント: