Objective-CとCocoaの立場から、デザインパターンを眺めてみるこの取り組みも、いよいよ佳境に入ってきた。残すところ数パターンとなってきた。だが、まだまだクセの強いパターンが残っている。

今回からは、Interpreterパターンを取り上げよう。文法や言語を読み込むために使われるパターンだ。

Interpreterパターンとは

Interpreterパターンは、その名の通り、インタプリタと考えてしまえばいいだろう。インタプリタは、計算機の処理系の一種で、プログラミング言語などを読み込みながら処理するものだ。たとえば、BASICインタプリタや、Perlインタプリタなどがある。

このインタプリタのためのパターンが、Interpreterパターンだ。インタプリタそのものをデザインパターンにしてしまうとは、なかなか大胆な発想だ。

Interpreterパターンでは、文法規則の1つ1つをクラス化してしまうという、これまた大胆な方法を使う。GoF本では例として、Bool表現の文法を取り上げている。これをこの記事でも使おう。

次のような、Bool表現のための文法規則があるとする。

BooleanExp ::= VariableExp | Constant | OrExp | AndExp | NotExp | '('BooleanExp')'
AndExp ::= BooleanExp 'and' BooleanExp
OrExp ::= BooleanExp 'or' BooleanExp
NotExp ::= 'not' BooleanExp
Constant ::= 'YES' | 'NO'
VariableExp ::= 'A' | 'B' | ... | 'X' | 'Y' | 'Z'

これが、論理値のための文法規則となる。この文法を使えば、次のような表現を評価出来る訳だ。

(YES and x) or (y and (not x))

さて、Interpreterパターンでこの文法をどう取り扱うかというと、それぞれの文法をクラス化する。つまり、BooleanExpクラス、AndExpクラス、OrExpクラスなどを、順次作っていく訳だ。

これは確かに、文法のオブジェクト指向化という観点で見れば、非常に直接的なアプローチだ。だが、計算機科学の世界では、インタプリタやコンパイラを作るときは、文字列を直接パースして解析木を作り、変換していくアプローチの方が一般的であるし、パーサの作成も汎用化されている。

GoF本でも、Interpreterパターンを適用する際には、「文法が単純」であり、「効率が重要な関心事ではない」場合が適切であろう、と書かれている。つまり、速くて軽いインタプリタを作るのであれば、従来の手法の方がよい。それに対して、単純な文法で、変更や拡張が頻繁に行われるような場合に、Interpreterパターンが登場する余地がある、ということだろう。

Bool表現のためのインタプリタ実装

では、InterpreterパターンのObjective-Cでの実装例を紹介しよう。

取り上げるのは、先ほど紹介したBool表現だ。GoF本では、実装の一部のみが紹介されている。ここでは、これを実現する完全な実装をやってみよう。ソースコードが長くなるが、ご容赦いただきたい。

作成するクラスは、文法のためのルートクラスになるBooleanExp。そのサブクラスであるAndExp、OrExp、NotExp、Constant、VariableExp。そして、変数に代入された値を管理するContextだ。

クラス図は、次のようになる。

では、作っていこう。まず、BooleanExpクラスだ。これは、文法のルートとなるクラスである。文法の主目的は、与えられた文を評価することだ。そこで、evaluate:というメソッドを1つ用意しよう。引数は、Contextクラスになる。返り値は、Bool値の文の評価はYESかNOかになるので、Bool型を返す。

List 1.

@interface BooleanExp : NSObject
{
}

- (BOOL)evaluate:(Context*)context;

@end

このクラスは、ルートクラスなので、実際には直接使われることはない。そこで、evaluateメソッドの実装では、NOを返すだけにしておく。

次は、AndExpクラスだ。これは、論理積の文法を表すクラスだ。論理積を計算するには、2つの論理値が必要になる。そこで、初期化メソッドで、この値を与えることにしよう。

List 2.

@interface AndExp : BooleanExp
{
    BooleanExp* _operand1;
    BooleanExp* _operand2;
}

- (id)initWithOperand1:(BooleanExp*)operand1 operand2:(BooleanExp*)operand2;

@end

AndExpメソッドのevaluate:メソッドでは、これらを評価して論理積を計算することになる。つまり、AndExpの実装は次のようになるだろう。

List 3.

@implementation AndExp

- (id)initWithOperand1:(BooleanExp*)operand1 operand2:(BooleanExp*)operand2
{
    self = [super init];
    if (!self) {
        return nil;
    }

    // Operand1とOperand2を設定する。
    _operand1 = [operand1 retain];
    _operand2 = [operand2 retain];

    return self;
}

- (BOOL)evaluate:(Context*)context
{
    // Operand1とOperand2のANDの値を返す。
    return [_operand1 evaluate:context] && [_operand2 evaluate:context];
}

@end

同様に、論理和のためのOrExpと、否定のためのNotExpの定義、実装は、次のようになる。

List 4.

@interface OrExp : BooleanExp
{
    BooleanExp* _operand1;
    BooleanExp* _operand2;
}

- (id)initWithOperand1:(BooleanExp*)operand1 operand2:(BooleanExp*)operand2;

@end

@interface NotExp : BooleanExp
{
    BooleanExp* _operand;
}

- (id)initWithOperand:(BooleanExp*)operand;

@end

List 5.

@implementation OrExp

- (id)initWithOperand1:(BooleanExp*)operand1 operand2:(BooleanExp*)operand2
{
    self = [super init];
    if (!self) {
        return nil;
    }

    // Operand1とOperand2を設定する。
    _operand1 = [operand1 retain];
    _operand2 = [operand2 retain];

    return self;
}

- (BOOL)evaluate:(Context*)context
{
    // Operand1とOperand2のORの値を返す。
    return [_operand1 evaluate:context] || [_operand2 evaluate:context];
}

@end

@implementation NotExp

- (id)initWithOperand:(BooleanExp*)operand
{
    self = [super init];
    if (!self) {
        return nil;
    }

    // Operandを設定する。
    _operand = [operand retain];

    return self;
}

- (BOOL)evaluate:(Context*)context
{
    // OperandのNOTの値を返す。
    return ![_operand evaluate:context];
}

@end

次は、定数を表すConstantだ。このクラスは、論理値を引数として取る。

List 6.

@interface Constant : BooleanExp
{
    BOOL    _value;
}

- (id)initWithValue:(BOOL)value;

@end

ということは、評価は簡単だ。この保持している論理値の値を返すだけである。

List 7.

@implementation Constant

- (id)initWithValue:(BOOL)value
{
    self = [super init];
    if (!self) {
        return self;
    }

    // valueを設定する。
    _value = value;

    return self;
}

- (BOOL)evaluate:(Context*)context
{
    // valueの値をそのまま返す。
    return _value;
}

@end

次は、変数を表すVariableExpだ。これは、変数名を指定して初期化することにする。

List 8.

@interface VariableExp : BooleanExp
{
    NSString*   _name;
}

- (id)initWithName:(NSString*)name;

@end

変数の評価をどうするかだが、ここでContextを使う。Contextには、現在の変数のそれぞれの値が設定されているのだ。変数の名前を渡して、現在の値を取得することが出来る。

List 9.

@implementation VariableExp

- (id)initWithName:(NSString*)name
{
    self = [super init];
    if (!self) {
        return nil;
    }

    // 変数名を設定する。
    _name = [name retain];

    return self;
}

- (BOOL)evaluate:(Context*)context
{
    // コンテキストから、変数名を指定して値を取得する。
    return [context lookupWithName:_name];
}

@end

最後は、Contextだ。これは、変数名とその値を管理しておくためのものだ。そのために、NSMutableDictionary型の辞書を1つ用意しておこう。変数の値の取得はlookupWithName:で、設定はassignValue:toName:で行う。

List 10.

@interface Context : NSObject
{
    NSMutableDictionary*    _nameDict;
}

- (BOOL)lookupWithName:(NSString*)name;
- (void)assignValue:(BOOL)value toName:(NSString*)name;

@end

実装は、次のようになるだろう。それぞれのメソッドで、変数名をキーとして辞書にアクセスすることになる。

List 11.

@implementation Context

- (id)init
{
    self = [super init];
    if (!self) {
        return self;
    }

    // インスタンス変数を初期化する。
    _nameDict = [[NSMutableDictionary dictionary] retain];

    return self;
}

- (BOOL)lookupWithName:(NSString*)name
{
    // 変数名の辞書から値を取得する。
    return [[_nameDict objectForKey:name] boolValue];
}

- (void)assignValue:(BOOL)value toName:(NSString*)name
{
    // 変数名の辞書に値を設定する。
    [_nameDict setObject:[NSNumber numberWithBool:value] forKey:name];
}

@end

これで、実装は完了だ。実際に使ってみよう。次の文を評価するものとする。

(YES and x) or (y and (not x))

評価の手順は次のようになる。まず、変数xとyを作成しよう。これは、VariableExpクラスを作ることになる。さらに、定数'YES'を作る。これはConstantクラスだ。

これらに論理演算を適用していこう。まず、'not x'だ。これはNotExpクラスを作り、オペランドとして、xを渡してやる。同様に、'YES and x'、'y and (not x)'と作っていこう。最後に、論理和を作れば式の出来上がりだ。

次に、変数に値を設定してやる必要がある。このためにContextクラスを作ろう。xとyに、それぞれ論理値を設定してやる。

これで準備が完了だ。最後に、評価を行う。評価は、Contextを引数として、evaluate:メソッドを呼べばいい。これで、式の評価を、YESまたはNOで得ることが出来る。

List 12.

    // 変数'x'と'y'を作成する。
    VariableExp*    x;
    VariableExp*    y;
    x = [[VariableExp alloc] initWithName:@"X"];
    y = [[VariableExp alloc] initWithName:@"Y"];

    // 定数'YES'を作成する。
    Constant*   yes;
    yes = [[Constant alloc] initWithValue:YES];

    // 'not x'を表す式を作成する。
    NotExp* notExp;
    notExp = [[NotExp alloc] initWithOperand:x];

    // 'YES and x'を表す式を作成する。
    AndExp* andExp1;
    andExp1 = [[AndExp alloc] initWithOperand1:yes operand2:x];

    // 'y and not(x)'を表す式を作成する。
    AndExp* andExp2;
    andExp2 = [[AndExp alloc] initWithOperand1:y operand2:notExp];

    // '(YES and x) or (y and not(x))'を表す式を作成する。
    BooleanExp* exp;
    exp = [[OrExp alloc] initWithOperand1:andExp1 operand2:andExp2];

    // 変数'x'にYESを、変数'y'にNOを設定する。
    Context*    context;
    context = [[Context alloc] init];
    [context assignValue:YES toName:@"X"];
    [context assignValue:NO toName:@"Y"];

    // 式を評価する。
    BOOL    result;
    result = [exp evaluate:context];

ずいぶん長くなってしまったが、これがInterpreterパターンのObjective-Cでの実装例だ。文法がそのままクラスに転換されている様子が分かったと思う。