Паттерн Interpreter (интерпетатор)
Назначение паттерна Interpreter
- Для заданного языка определяет представление его грамматики, а также интерпретатор предложений этого языка.
- Отображает проблемную область в язык, язык – в грамматику, а грамматику – в иерархии объектно-ориентированного проектирования.
Решаемая проблема
Пусть в некоторой, хорошо определенной области периодически случается некоторая проблема. Если эта область может быть описана некоторым “языком“, то проблема может быть легко решена с помощью “интерпретирующей машины“.
Обсуждение паттерна Interpreter
Паттерн Interpreter определяет грамматику простого языка для проблемной области, представляет грамматические правила в виде языковых предложений и интерпретирует их для решения задачи. Для представления каждого грамматического правила паттерн Interpreter использует отдельный класс. А так как грамматика, как правило, имеет иерархическую структуру, то иерархия наследования классов хорошо подходит для ее описания.
Абстрактный базовый класс определяет метод interpret()
, принимающий (в качестве аргумента) текущее состояние языкового потока. Каждый конкретный подкласс реализует метод interpret()
, добавляя свой вклад в процесс решения проблемы.
Структура паттерна Interpreter
Паттерн Interpreter моделирует проблемную область с помощью рекурсивной грамматики. Каждое грамматическое правило может быть либо составным (правило ссылается на другие правила) либо терминальным (листовой узел в структуре ”дерево”).
Для рекурсивного обхода ”предложений” при их интерпретации используется паттерн Composite.
UML-диаграмма классов паттерна Interpreter
Пример паттерна Interpreter
Паттерн Intepreter определяет грамматическое представление для языка и интерпретатор для интерпретации грамматики. Музыканты являются примерами интерпретаторов. Тональность и продолжительность звуков могут быть описаны нотами. Такое представление является музыкальным языком. Музыканты, используя ноты, способны воспроизвести оригинальные частоту и длительность каждого представленного звука.
Использование паттерна Interpreter
- Определите “малый“ язык, “инвестиции” в который будут оправданными.
- Разработайте грамматику для языка.
- Для каждого грамматического правила (продукции) создайте свой класс.
- Полученный набор классов организуйте в структуру с помощью паттерна Composite.
- В полученной иерархии классов определите метод
interpret(Context)
. - Объект
Context
инкапсулирует информацию, глобальную по отношению к интерпретатору. Используется классами во время процесса ”интерпретации”.
Особенности паттерна Interpreter
- Абстрактное синтаксическое дерево интерпретатора – пример паттерна Composite.
- Для обхода узлов дерева может применяться паттерн Iterator.
- Терминальные символы могут разделяться c помощью Flyweight.
- Паттерн Interpreter не рассматривает вопросы синтаксического разбора. Когда грамматика очень сложная, должны использоваться другие методики.
Реализация паттерна 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' | 'L' | 'V'
one ::= 'M' | 'C' | 'X' | 'I'
Для проверки и интерпретации строки используется иерархия классов с общим базовым классом RNInterpreter, имеющим 4 под-интерпретатора. Каждый под-интерпретатор получает "контекст" (оставшуюся неразобранную часть строки и накопленное вычисленное значение разобранной части) и вносит свой вклад в процесс обработки. Под-переводчики просто определяют шаблонные методы, объявленные в базовом классе RNInterpreter.
#include <iostream.h>
#include <string.h>
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 (!strncmp(input, nine(), 2))
{
total += 9 * multiplier();
index += 2;
}
else if (!strncmp(input, four(), 2))
{
total += 4 * multiplier();
index += 2;
}
else
{
if (input[0] == five())
{
total += 5 * multiplier();
index = 1;
}
else
index = 0;
for (int end = index + 3; index < end; index++)
if (input[index] == one())
total += 1 * multiplier();
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): RNInterpreter(1){}
protected:
char one()
{
return 'M';
}
char *four()
{
return "";
}
char five()
{
return '\0';
}
char *nine()
{
return "";
}
int multiplier()
{
return 1000;
}
};
class Hundred: public RNInterpreter
{
public:
Hundred(int): RNInterpreter(1){}
protected:
char one()
{
return 'C';
}
char *four()
{
return "CD";
}
char five()
{
return 'D';
}
char *nine()
{
return "CM";
}
int multiplier()
{
return 100;
}
};
class Ten: public RNInterpreter
{
public:
Ten(int): RNInterpreter(1){}
protected:
char one()
{
return 'X';
}
char *four()
{
return "XL";
}
char five()
{
return 'L';
}
char *nine()
{
return "XC";
}
int multiplier()
{
return 10;
}
};
class One: public RNInterpreter
{
public:
One(int): RNInterpreter(1){}
protected:
char one()
{
return 'I';
}
char *four()
{
return "IV";
}
char five()
{
return 'V';
}
char *nine()
{
return "IX";
}
int multiplier()
{
return 1;
}
};
RNInterpreter::RNInterpreter()
{
// use 1-arg ctor to avoid infinite loop
thousands = new Thousand(1);
hundreds = new Hundred(1);
tens = new Ten(1);
ones = new One(1);
}
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()
{
RNInterpreter 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
Источник: http://sourcemaking.com/design_patterns/interpreter