ゲーム作成に役立つオブジェクト指向5

今回はInterpreterパターンについてです。
 
Interpreterというのは、
例えばテキストファイルに書かれた命令を、
実行時に一行ずつ解読しながらその内容を実行する、
というものです。
 
例えば、インタプリタは、

といった使い方があります。
 
具体的には、
スクリプトの書かれたテキストファイルを外部に用意しておき、実行時にそれを読ませる、
という仕組みです。
 
これにより、

などといった、「開発の負担」を減らす効果があります。
 
それをオブジェクト指向でどう実装するか、
というのがInterpreterパターンです。
 
 

インタプリタの命令のパターン

Interpreterの命令には、ある一定のパターンがあります。
 
それは、一行の命令は、

  • 構文の制御
  • 命令の実行

のどちらかである、ということです。
 
「構文の制御」というのは、C言語で言えば、「while/if」などの命令です。
これらの命令は何かを実行するというよりも、構文そのものをどう扱うかを定義するものです。
 
「命令の実行」とは、実際に「3歩動け!」とか「弾を撃て!」とかの命令のことです。
 

それで、クラス図は?

インタプリタパターン
ということで、クラス図なのですが、
どちらも同じ命令ということで、Node抽象クラスを基底に持ち、
「構文の制御」がNonterminalNodeクラス、
「命令の実行」がTerminalNodeクラスとなります。
Terminalとは終着駅のことで、「命令の実行」は構文の解析を一時停止するため、
このような名称となっています。
 
「じゃあ、その命令はどこからもらうの?」
 
ということなのですが、それがparseメソッドです。
 
parseメソッドはContextクラスを受け取ります。
このContextクラスが、命令が書かれたテキストファイルの読み込んだクラスです。
 
Nodeクラスの派生クラスは、Contextを解析して、命令を実行したりします。
 
ちなみに、
なぜ「Node」という名称がついているか、
ということなのですが、
例えば、「構文の制御」が「構文の制御」を呼んだ場合、
どんどん処理がネストしていくことになります。
 
こうやってできた「節点」をNodeと呼ぶからです。

処理の流れは?

では、どのように処理を実行していくのかを説明します。

  1. Contextにスクリプトを読み込ませる
  2. NonTerminalNodeにContextを渡す
  3. NonTerminalNodeの中で、現在行のスクリプトを判定する
    • 「命令の実行」であれば、TerminalNodeにContextを渡し、命令を実行。
    • 「命令の制御」であれば、NonterminalNodeにContextを渡す->3に戻る。

2についてなのですが、スクリプトの仕様として、
実行のエントリポイントとして「トップノード」を用意しておきます。
 
こうすることで、Nodeクラスの呼び出し側としては、
NonterminalNodeのparseメソッドを呼ぶだけ、
という簡潔な処理になります。
 

問題点

といった感じがInterpreterパターンなのですが、
実は問題点が2つあります。
 
1つは、
このクラス設計ですと、

  • Node派生クラスに「命令実行」の処理を書かなければならない

ということです。
Node派生クラスの役割は、「構文の解析」でありますから、
これでは、クラスが肥大化してしまいますよね。
 
そこで、
これを解決するためにはVisitorパターンを使います。
(それについては次回で…)
 
もう1つの問題点は、

  • 制御の中断

です。
 
この方法は、構文の解析をどんどんネストしています。
こうすると、構文の解析が終了するまで、ゲームループに制御が戻ってきませんよね。
 
それは困ったものです。
 
で、解決策としては、

  • マイクロスレッド(ファイバ)
  • 解析履歴を保持しておき、解析を中断する

という方法があります。
 
ゲームループに戻すためには、ネストを抜ける必要がありますので、
スタックを無理やり抜ける「マイクロスレッド」か、
解析履歴を持たせておけば、次回の呼び出し時に解析を再開できます。
 
 
といった感じですね…。
次回はVisitorパターンについてです。