Паттерн Interpreter (Интерпетатор)

Название и классификация паттерна

Интерпретатор — паттерн поведения классов.

Назначение паттерна Interpreter

Для заданного языка определяет представление его грамматики, а также интерпретатор предложений этого языка.

Отображает проблемную область в язык, язык — в грамматику, а грамматику — в иерархии объектно-ориентированного проектирования.

Обсуждение паттерна Interpreter

Паттерн Interpreter определяет грамматику простого языка для проблемной области, представляет грамматические правила в виде языковых предложений и интерпретирует их для решения задачи. Для представления каждого грамматического правила паттерн Interpreter использует отдельный класс. А так как грамматика, как правило, имеет иерархическую структуру, то иерархия наследования классов хорошо подходит для ее описания.

Абстрактный базовый класс определяет метод interpret), принимающий (в качестве аргумента) текущее состояние языкового потока. Каждый конкретный подкласс реализует метод interpret), добавляя свой вклад в процесс решения проблемы.

Структура паттерна Interpreter

Паттерн Interpreter моделирует проблемную область с помощью рекурсивной грамматики. Каждое грамматическое правило может быть либо составным (правило ссылается на другие правила), либо терминальным (листовой узел в структуре «дерево»).

Для рекурсивного обхода «предложений» при их интерпретации используется паттерн Composite.

UML-диаграмма классов паттерна Interpreter

Структура паттерна Интерпретатор показана на рис. 65.

Участники

AbstractExpression — абстрактное выражение: объявляет абстрактную операцию Interpret, общую для всех узлов в абстрактном синтаксическом дереве.

I

// perform "parent" functionality // then delegate to each "child" element

// "Context" is data structure for // holding input and output

Рис. 65. UML-диаграмма паттерна Интерпретатор

TerminalExpression — терминальное выражение:

  • • реализует операцию Interpret для терминальных символов грамматики;
  • • необходим отдельный экземпляр для каждого терминального символа в предложении.

CompoundExpression — нетерминальное выражение:

  • • по одному такому классу требуется для каждого грамматического правила R :: — Rl, R2,..., Rn;
  • • хранит переменные экземпляра типа AbsftactExpression для каждого символа от RI до Rn
  • • реализует операцию Interpret для нетерминальных символов грамматики. Эта операция рекурсивно вызывает себя же для переменных, представляющих Rl, R2, ..., Rn.

Context — контекст: содержит информацию, глобальную по отношению к Интерпретатору.

Client — клиент:

  • • строит (или получает в готовом виде) абстрактное синтаксическое дерево, представляющее отдельное предложение на языке с данной грамматикой. Дерево составлено из экземпляров классов CompoundExpression и TerminalExpression;
  • • вызывает операцию Interpret.

Достоинства и недостатки

Грамматику легко изменять и расширять. Поскольку для представления грамматических правил в паттерне используются классы,

то для изменения или расширения грамматики можно применять наследование. Существующие выражения можно модифицировать постепенно, а новые определять как вариации старых.

Простая реализация грамматики. Реализации классов, описывающих узлы абстрактного синтаксического дерева, похожи. Такие классы легко кодировать, а зачастую их может автоматически сгенерировать компилятор или генератор синтаксических анализаторов.

Сложные грамматики трудно сопровождать. В паттерне Интерпретатор определяется по меньшей мере один класс для каждого правила грамматики (для правил, определенных с помощью формы Бэ-куса—Наура — BNF, может понадобиться и более одного класса). Поэтому сопровождение грамматики с большим числом правил иногда оказывается трудной задачей. Но если грамматика очень сложна, лучше прибегнуть к другим методам, например воспользоваться генератором компиляторов или синтаксических анализаторов.

Использование паттерна Interpreter

Определите «малый» язык, «инвестиции» в который будут оправданными.

Разработайте грамматику для языка.

Для каждого грамматического правила (продукции) создайте свой класс.

Полученный набор классов организуйте в структуру с помощью паттерна Composite.

В полученной иерархии классов определите метод interpret (Context).

Объект Context инкапсулирует информацию, глобальную по отношению к Интерпретатору. Используется классами во время процесса «интерпретации».

Реализация паттерна Interpreter

Совместное использование паттернов Interpreter и Template Method

Рассмотрим задачу интерпретирования (вычисления) значений строковых представлений римских чисел. Используем следующую грамматику.

romanNumeral ::= {thousands} {hundreds} {tens} {ones} thousands,hundreds,tens,ones ::= nine | four | {five} {one} {one} {one} nine ::= "CM" | "XC" | "IX" four ::= "CD" | "XL" | "IV five ::= 'D' I 'L' I V

one 'CI'X’IT

Для проверки и интерпретации строки используется иерархия классов с общим базовым классом RNInterpreter, имеющим 4 подинтерпретатора. Каждый подинтерпретатор получает «контекст» (оставшуюся неразобранную часть строки и накопленное вычисленное значение разобранной части) и вносит свой вклад в процесс обработки. Подпереводчики просто определяют шаблонные методы, объявленные в базовом классе RN Interpreter.

#include

#include

class Thousand; class Hundred; class Ten; class One;

class RNInterpreter

{

public:

RNInterpreter(); // ctor for client RNInterpreter(int){}

// ctor for subclasses, avoids infinite loop int interpret(char*); // interpret) for client virtual void interpret(char *input, int &total)

{

// for internal use int index; index = 0;

if (!stmcmp(input, nine(), 2))

{

total += 9 * multiplied); index += 2;

}

else if (!strncmp(input, four(), 2))

{

total += 4 * multiplied); index += 2;

}

else

{

if (input[0] == five())

{

total += 5 * multiplied); index = 1;

}

else

index = 0;

for (int end = index + 3; index < end; index++) if (input[index] == one()) total += 1 * multiplierO; else break;

}

strcpy(input, &(input[index|));

} // remove leading chars processed protected:

// cannot be pure virtual because client asks for instance virtual char one(){} virtual char *four(){} virtual char five(){} virtual char *nine(){} virtual int multiplier(){} private:

RNInterpreter thousands;

RNInterpreter *hundreds;

RNInterpreter *tens;

RNInterpreter *ones;

class Thousand: public RNInterpreter

{

public:

// provide 1 -arg ctor to avoid infinite loop in base class ctor Thousand(int): RN Interpreter! 1){} protected: char one()

{

return 'M';

}

char *four()

{

return

}

char five()

{

return '';

}

char *nine()

{

return

}

int multiplied)

{

return 1000;

}

};

class Hundred: public RNInterpreter

{

public:

Hundred(int): RN Interpreted 1){} protected: char one()

{

return 'C;

}

char *four()

{

return "CD";

}

char five()

{

return 'D';

}

char *nine()

{

return "CM";

}

int multiplied)

{

return 100;

}

};

class Ten: public RNInterpreter

{

public:

Ten(int): RN Interpreted 1){} protected: char one()

{

return 'X';

char *four()

{

return "XL";

}

char five()

{

return 'L';

}

char *nine()

{

return "XC";

}

int multiplied)

{

return 10;

}

class One: public RNInterpreter

{

public:

One(int): RN Interpreted 1){} protected: char one()

{

return T;

}

char *four()

{

return "IV";

}

char five()

{

return 'V;

}

char *nine()

{

return "IX";

}

int multiplied)

{

return 1;

RN Interpreter:: RN Interpreted)

{

// use 1 -arg ctor to avoid infinite loop thousands = new Thousand! 1 ); hundreds = new Hundred! 1); tens = new Ten(l); ones = new One(l);

}

int RNInterpreter::interpret(char *input)

{

int total; total = 0;

thousands->interpret(input, total); hundreds->interpret(input, total); tens->interpret(input, total); ones->interpret(input, total); if (strcmp(input, ""))

// if input was invalid, return 0 return 0; return total;

} int main!)

{

RN Interpreter interpreter; char input[20];

cout << "Enter Roman Numeral:"; while (cin » input)

{

cout << " interpretation is"

<< interpreter.interpret(input) << endl; cout << "Enter Roman Numeral:";

}

Вывод программы:

Enter Roman Numeral: MCMXCVI interpretation is 1996 Enter Roman Numeral: MMMCMXCIX interpretation is 3999 Enter Roman Numeral: MMMM interpretation is 0

Enter Roman Numeral: MDCLXVIIII

interpretation is 0

Enter Roman Numeral: CXCX interpretation is 0

Enter Roman Numeral: MDCLXVI interpretation is 1666

Enter Roman Numeral: DCCCLXXXVIII interpretation is 888

Родственные паттерны

Компоновщик: абстрактное синтаксическое дерево — это пример применения паттерна Компоновщик.

Приспособленец показывает варианты разделения терминальных символов в абстрактном синтаксическом дереве.

Итератор: Интерпретатор может пользоваться Итератором для обхода структуры.

Посетителя можно использовать для инкапсуляции в одном классе поведения каждого узла абстрактного синтаксического дерева.

 
< Пред   СОДЕРЖАНИЕ     След >