C#
デザインパターン
Interpreter

デザインパターン勉強会 第二十三回:Interpreterパターン

はじめに

本エントリーは某社内で実施するデザインパターン勉強会向けの資料となります。
本エントリーで書籍「Java言語で学ぶデザインパターン入門」をベースに学習を進めますが、サンプルコードはC#に置き換えて解説します。
なお過去分はこちら

第一回:Iteratorパターン
第二回:Adapterパターン
第三回:Template Methodパターン
第四回:Factory Methodパターン
第五回:Singletonパターン
第六回:Prototypeパターン
第七回:Builderパターン
第八回:Abstract Factoryパターン
第九回:Bridgeパターン
第十回:Strategyパターン
第十一回:Compositeパターン
第十二回:Decoratorパターン
第十三回:Visitorパターン
第十四回:Chain of Responsibilityパターン
第十五回:Facadeパターン
第十六回:Mediatorパターン
第十七回:Observerパターン
第十八回:Mementoパターン
第十九回:Stateパターン
第二十回:Flyweightパターン
第二十一回:Proxyパターン
第二十二回:Commandパターン


Interpreterパターンとは

Interpreterとは、英語で解釈者、説明者、通訳者といった意味を持ちます。
Interpreterパターンは、実装言語とは異なる何らかのフォーマットで記載された命令を解析して、言語の文法をオブジェクトで表現するパターンです。


Interpreterパターンの目的

主に二つの方向性があると考えます。

  1. プログラムを外部から動的に制御可能な仕組みの実現
  2. 特定ドメインで問題を高効率で解決するため(SQLなど)

典型的な設計パターン

典型的なInterpreterパターンの構造は以下の通りです。

Interpreter パターン.png

Contextが「実装言語とは異なる何らかのフォーマット」で記載された実態を表します。
それをAbstractExpressionのConcreteクラスであるInterpreterが解析しつつ実行します。
Clientは処理を呼び出し元になります。


例題

逆ポーランド記法を利用した四則演算などをよく見かけますが、ここではXMLで記載された四則演算を実行する例を紹介しましょう。
なぜXMLか?文字列のパースが楽だからです。今回のパターンの本質は難しいインタープリター言語の実装ではないため、パースはできるだけ楽をします。

次が実際のXMLで書かれた四則演算です。普通に+-書いた方が圧倒的に短いだろうという突っ込みは禁止です。

<?xml version="1.0"?>
<Addition>
  <Subtraction>
    <Constant Value="3" />
    <Constant Value="1" />
  </Subtraction>
  <Multiplication>
    <Division>
      <Constant Value="6" />
      <Constant Value="2" />
    </Division>
    <Constant Value="5" />
  </Multiplication>
</Addition>

四則演算がXMLでツリー上に定義されており、ツリーを下から遡りながら演算します。演算はElementの名称が表します。

演算 名称
足し算 Addition
引き算 Subtraction
掛け算 Multiplication
割り算 Division
定数 Constant

つまり上のXMLは次の計算を表します。
(3 - 1) + ((6 / 2) * 5)
17が計算できれば成功です。

サンプルクラス図

という訳で、次がそのクラス図になります。

Interpreter Pattern.png

CalcExpressionがコンポジットパターンになっているのが見て取れます。


各クラスの役割

名前 役割
Program インタープリターを利用するクラス
Context XMLで記述された四則演算言語をオブジェクトに解釈するクラス
IExpression なんらかの処理(表現)を表すインターフェース
CalcMethod 計算方式を表すEnum
CalcExpression 四則演算を表すクラス
Constant 定数を表すクラス

各種クラスの実装


Program

XMLからContextを読み込み、ContextからIExpressionを生成します。
その後、IExpressionを実行して結果をコンソールへ出力しています。
XMLからオブジェクトへのデシリアライズにはXmlSerializerを利用しています。

class Program
{
    static void Main()
    {
        var context = new Context("expressions.xml");
        var expression = context.BuildExpression();
        Console.WriteLine($"expression.Operate() : {expression.Operate()}");
        Console.ReadLine();
    }
}

Context

XMLで表現された四則演算言語のオブジェクトに解釈するクラスです。
BuildExpressionメソッドでXMLの構文を解析してIExpressionを構築しています。

public class Context
{
    private readonly string _fileName;

    public Context(string fileName)
    {
        _fileName = fileName;
    }

    public IExpression BuildExpression()
    {
        var xmlDocument = new XmlDocument();
        xmlDocument.Load(_fileName);
        return BuildExpression(xmlDocument.DocumentElement);
    }

    public IExpression BuildExpression(XmlElement element)
    {
        if (element.Name == "Constant")
        {
            return new Constant(int.Parse(element.GetAttribute("Value")));
        }
        else
        {
            // 子Elementを再帰的に探索してIExpressionを生成する
            var expressions =
                (from object childNode in element.ChildNodes
                    where childNode is XmlElement
                    select BuildExpression((XmlElement) childNode)).ToList();
            return
                new CalcExpression(
                    (CalcMethod)Enum.Parse(typeof(CalcMethod), element.Name, true),
                    expressions);
        }
    }
}

CalcMethod

計算方法を表すメソッドです。

public enum CalcMethod
{
    Addition,
    Subtraction,
    Multiplication,
    Division
}

CalcExpression

四則演算を実施するクラスです。
引数で渡されたCalcMethodに従って計算を実施します。

public class CalcExpression : IExpression
{
    private readonly Func<int, int, int> _calc;
    private readonly IList<IExpression> _expressions;

    public CalcExpression(CalcMethod calcMethod, IList<IExpression> expressions)
    {
        _expressions = expressions;
        switch (calcMethod)
        {
            case CalcMethod.Addition:
                _calc = (x, y) => x + y;
                break;
            case CalcMethod.Subtraction:
                _calc = (x, y) => x - y;
                break;
            case CalcMethod.Multiplication:
                _calc = (x, y) => x * y;
                break;
            case CalcMethod.Division:
                _calc = (x, y) => x / y;
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(calcMethod), calcMethod, null);
        }
    }

    public int Operate()
    {
        var result = _expressions.First().Operate();
        for (var i = 1; i < _expressions.Count; i++)
        {
            result = _calc(result, _expressions[i].Operate());
        }
        return result;
    }
}

Constant

定数

public class Constant : IExpression
{
    private readonly int _value;

    public Constant(int value)
    {
        _value = value;
    }

    public int Operate()
    {
        return _value;
    }
}

Interpreterパターンまとめ

  • Interpreterパターンは、実装言語とは異なる何らかのフォーマットで記載された命令を解析して、言語の文法をオブジェクトで表現するパターン
  • おもな使用用途に、次のようなケースが考えられる
    • ドメイン専用の言語を作成し、物事を高効率で解決する(SQLなど)
    • 命令を設定ファイルや外部入力から得ることで動的な振る舞いを実現する

サンプルコード

以下に公開しています。

https://github.com/RJ-DesignPattern/DesignPattern/tree/master/InterpreterPattern