9. "ЖЕНЩИНА, ВАМ СЮДА"
Начнём с наиболее простой и очевидной функции WhereToPut. Эта функция определяет "куда послать" токен. Существуют всего три варианта:
- послать его "на выход" (в выходную последовательность);
- послать его в "отстойник" (в стек);
- послать его
на ....т.е., в "никуда" (попросту "съесть" токен, никуда не направляя).
Место, куда следует послать токен, однозначно задаётся его видом:
- токен следует послать "на выход", если он является:
- операндом (любым);
- .............и т.д. ......................;
- ... если что, то добавим .....
- токен следует послать в стек, если он является:
- операцией (любой);
- открывающей скобкой;
- .............и т.д. ......................;
- ... если что, то добавим .....
- токен следует послать в "никуда", если он является:
- закрывающей скобкой;
- концом входной последовательности;
- .............и т.д. ......................;
- ... если что, то добавим .....
Для реализации функции WhereToPut заведём внешнюю по отношению к функции таблицу этих правил (массив), которая будет сопоставлять вид токена с местом его положения:
enum TTokenType // Это справочник типов токенов
{
eTokenOperand = 0, // операнд
eTokenPlus, // операция сложение
eTokenMinus, // операция вычитание
eTokenMultiply, // операция умножение
eTokenDivide, // операция деление
eTokenOpeningBracket, // открывающая скобка
eTokenClosingBracket, // закрывающая скобка
eTokenEndOfSequence, // конец последовательности
eTokenTypeCount // количество типов токенов
}
enum TTokenPlace // Это справочник "адресов" (куда токены направляются)
{
eOutputSequence=0, // выходная последовательность
eOperationStack, // стек операций
eNowhere // "никуда"
};
int g_TokenPlaces[eTokenTypeCount] = // В зависимости от идентификатора токена, совершается действие...
{
eOutputSequence, // операнд помещается в выходную последовательность
eOperationStack, // плюс помещается в стек
eOperationStack, // минус помещается в стек
eOperationStack, // умножение помещается в стек
eOperationStack, // деление помещается в стек
eOperationStack, // открывающая скобка помещается в стек
eNowhere, // закрывающая скобка никуда не помещается
eNowhere // конец последовательности никуда не помещается
};
И тогда код функции WhereToPut будет выглядеть лишь как обращение к справочной таблице:
int WhereToPut(const TToken & rToken)
{
assert(rToken.GetType() < eTokenTypeCount);
return g_TokenPlaces[rToken.GetType()];
}
Учитывая, что "assert" - это макрос проверки условия и обработки ошибок, то тело функции WhereToPut - это лишь строка:
return g_TokenPlaces[rToken.GetType()];//Узнай местоположение токена из таблицы g_TokenPlaces[]
При добавлении токенов новых видов этот код не поменяется. Изменится лишь количество элементов в справочной таблице g_TokenPlaces[].
Методическое отступление: По нашему мнению, это важно для удобства последующего сопровождения программы и последующего развития ее функциональности: одно дело в дальнейшем развивать функциональность, добавляя строки во внешний справочник, абсолютно не меняя основного кода программы, другое дело - вносить изменения в основной код (последнее гораздо хуже).
Если Читатель преподает принципы проектирования программ, мы бы посоветовали и отсюда почерпнуть микропример, иллюстрирующий принципы "идеальной программной архитектуры", приведенные в начале статьи:
1) Идеально - это когда добавление новой функциональности в программу не потребует внесения изменений и/или добавлений в программный код (более того, в идеальной программе увеличение функциональности приводит к сокращению кода).
2) Идеально - это когда программу сможет в отсутствие Автора не только сопровождать, но и развивать программист с меньшей квалификацией, чем Автор программы.
В данном примере, мы уровень идеальности повысили.
Далее...