Главная страница
Навигация по странице:

  • RegularExpression

  • TerminalExpression

  • NonterminalExpression

  • Context

  • Э. Гамма, Р. Хелм


    Скачать 6.37 Mb.
    НазваниеЭ. Гамма, Р. Хелм
    АнкорFactorial
    Дата14.03.2022
    Размер6.37 Mb.
    Формат файлаpdf
    Имя файлаPatterny_Obektno-Orientirovannogo_Proektirovania_2020.pdf
    ТипДокументы
    #395452
    страница24 из 38
    1   ...   20   21   22   23   24   25   26   27   ...   38

    283
    Пример кода
    Приведенный ниже код на языке C++ дает представление о реализации классов
    Command
    , обсуждавшихся в разделе «Мотивация». Мы определим классы
    OpenCommand
    ,
    PasteCommand и
    MacroCommand
    . Начнем с абстрактного класса
    Command
    :
    class Command { public: virtual

    Command(); virtual void Execute() = 0; protected:
    Command();
    };
    Команда
    OpenCommand открывает документ, имя которому задает пользова- тель. Конструктору
    OpenCommand передается объект
    Application
    . Функция
    AskUser запрашивает у пользователя имя открываемого документа:
    class OpenCommand : public Command { public:
    OpenCommand(Application*); virtual void Execute(); protected:
    virtual const char* AskUser();
    private:
    Application* _application;
    char* _response;
    };
    OpenCommand::OpenCommand (Application* a) {
    _application = a;
    } void OpenCommand::Execute () {
    const char* name = AskUser();
    if (name != 0) {
    Document* document = new Document(name);
    _application->Add(document); document->Open();
    }
    }
    Команде
    PasteCommand должен передаваться объект
    Document
    , являющийся получателем. Он передается в параметре конструктора
    PasteCommand
    :
    class PasteCommand : public Command { public:
    PasteCommand(Document*);

    284
    Глава 5. Паттерны поведения virtual void Execute(); private:
    Document* _document;
    };
    PasteCommand::PasteCommand (Document* doc)
    {
    _document = doc;
    }
    void PasteCommand::Execute ()
    {
    _document->Paste();
    }
    Для простых команд, не допускающих отмены и не требующих аргументов, можно воспользоваться шаблоном класса для параметризации получателя.
    Определим для них шаблонный подкласс
    SimpleCommand
    , который пара- метризуется типом получателя
    Receiver и хранит связь между объектом- получателем и действием, представленным указателем на функцию класса:
    template
    class SimpleCommand : public Command {
    public: typedef void (Receiver::* Action)();
    SimpleCommand(Receiver* r, Action a) :
    _receiver(r), _action(a) { } virtual void Execute(); private:
    Action _action;
    Receiver* _receiver;
    };
    Конструктор сохраняет информацию о получателе и действии в соответ- ствующих переменных экземпляра. Операция
    Execute просто выполняет действие по отношению к получателю:
    template
    void SimpleCommand<Receiver>::Execute ()
    {
    (_receiver->*_action)();
    }
    Чтобы создать команду, которая вызывает операцию
    Action для экземпляра класса
    MyClass
    , клиент пишет следующий код:
    MyClass* receiver = new MyClass;
    // ...
    Command* aCommand =

    Паттерн Command (команда)
    285
    new SimpleCommand(receiver, &MyClass::Action);
    // ...
    aCommand->Execute();
    Имейте в виду, что такое решение годится только для простых команд. Для более сложных команд, которые отслеживают не только получателей, но и аргументы, и, возможно, состояние, необходимое для отмены операции, приходится порождать подклассы от класса
    Command
    Класс
    MacroCommand управляет выполнением последовательности подкоманд и предоставляет операции для добавления и удаления подкоманд. Задавать получателя не требуется, так как в каждой подкоманде уже определен свой получатель:
    class MacroCommand : public Command {
    public:
    MacroCommand();
    virtual MacroCommand();
    virtual void Add(Command*);
    virtual void Remove(Command*);
    virtual void Execute();
    private:
    List* _cmds;
    };
    Основой класса
    MacroCommand является его функция
    Execute
    . Она обходит все подкоманды и для каждой вызывает ее операцию
    Execute
    :
    void MacroCommand::Execute () {
    ListIterator i(_cmds);
    for (i.First(); !i.IsDone(); i.Next()) {
    Command* c = i.CurrentItem(); c->Execute();
    }
    }
    Обратите внимание: если бы в классе
    MacroCommand была реализована опе- рация отмены
    Unexecute
    , то при ее выполнении подкоманды должны были бы отменяться в порядке, обратном порядку их применения в реализации
    Execute
    Наконец, в классе
    MacroCommand должны быть операции для добавления и удаления подкоманд:
    void MacroCommand::Add (Command* c) {
    _cmds->Append(c);
    }

    286
    Глава 5. Паттерны поведения void MacroCommand::Remove (Command* c) {
    _cmds->Remove(c);
    }
    Известные применения
    Вероятно, впервые паттерн команда появился в работе Генри Либермана
    (Henry Lieberman) [Lie85]. В системе MacApp [App89] команды широко применяются для реализации допускающих отмену операций. В ET++
    [WGM88], InterViews [LCI+92] и Unidraw [VL90] также имеются классы, описываемые паттерном команда. Так, в библиотеке InterViews определен абстрактный класс
    Action
    , который определяет функциональность команд.
    Также определяется шаблон
    ActionCallback
    , параметризованный методом действия, который автоматически создает экземпляры подклассов команд.
    В библиотеке классов THINK [Sym93b] также используются команды для поддержки отмены операций. В THINK команды называются задачами
    (Tasks). Объекты
    Task передаются по цепочке обязанностей (263), пока не будут кем-то обработаны.
    Объекты команд в каркасе Unidraw уникальны в том отношении, что могут вести себя подобно сообщениям. В Unidraw команду можно послать другому объекту для интерпретации, результат которой зависит от объекта-полу- чателя. Более того, сам получатель может делегировать интерпретацию следующему объекту, обычно своему родителю. Это напоминает паттерн цепочка обязанностей. Таким образом, в Unidraw получатель вычисляется, а не хранится. Механизм интерпретации в Unidraw использует информацию о типе, доступную во время выполнения.
    Джеймс Коплиен описывает, как в языке C++ реализуются функторы — объекты, ведущие себя, как функции [Cop92]. Перегрузка оператора вызова operator()
    делает его использование более прозрачным. Смысл паттерна команда в другом — он устанавливает и поддерживает связь между полу- чателем и функцией (то есть действием), а не просто функцию.
    Родственные паттерны
    Паттерн компоновщик (196) можно использовать для реализации макро- команд.
    Паттерн хранитель (330) может сохранять состояние, необходимое команде для отмены ее действия.
    Команда, которую нужно копировать перед помещением в список истории, ведет себя, как прототип (146).

    Паттерн Interpreter (интерпретатор)
    287
    ПАТТЕРН INTERPRETER (ИНТЕРПРЕТАТОР)
    Название и классификация паттерна
    Интерпретатор — паттерн поведения классов.
    Назначение
    Для заданного языка определяет представление его грамматики, а также интерпретатор предложений этого языка.
    Мотивация
    Если некоторая задача встречается достаточно часто, то имеет смысл пред- ставить ее конкретные проявления в виде предложений на простом языке.
    После этого можно создать интерпретатор, который решает задачу, анали- зируя предложения этого языка.
    Например, поиск строк по образцу — весьма распространенная задача. Регу- лярные выражения — это стандартный язык для задания образцов поиска.
    Вместо того чтобы программировать специализированные алгоритмы для сопоставления строк с каждым образцом, алгоритм поиска может интерпре- тировать регулярное выражение, описывающее множество подходящих строк.
    Паттерн интерпретатор определяет грамматику простого языка, представляет предложения на этом языке и интерпретирует их. Для приведенного при- мера паттерн описывает определение грамматики и интерпретации языка регулярных выражений.
    Допустим, они описываются следующей грамматикой:
    expression ::= literal | alternation | sequence | repetition |
    '(' expression ')'
    alternation ::= expression '|' expression sequence ::= expression '&' expression repetition ::= expression '*'
    literal ::= 'a' | 'b' | 'c' | ... { 'a' | 'b' | 'c' | ... }*
    где expression
    — начальный символ, а literal
    — терминальный символ, определяющий простые слова.
    Паттерн интерпретатор использует класс для представления каждого правила грамматики. Символы в правой части правила — это переменные экзем- пляров таких классов. Для представления приведенной выше грамматики требуется пять классов: абстрактный класс
    RegularExpression и четыре его

    288
    Глава 5. Паттерны поведения подкласса
    LiteralExpression
    ,
    AlternationExpression
    ,
    SequenceExpression и
    RepetitionExpression
    . В последних трех подклассах определены пере- менные для хранения подвыражений.
    RegularExpression
    Interpret()
    Interpret()
    RepetitionExpression
    Interpret()
    Повторение
    Альтернатива1
    Альтернатива2
    Выражение1
    Выражение2
    AlternationExpression
    Interpret()
    SequenceExpression
    Interpret()
    literal
    LiteralExpression
    Каждое регулярное выражение, описываемое этой грамматикой, представля- ется в виде абстрактного синтаксического дерева, в узлах которого находятся экземпляры этих классов. Например, дерево
    aSequenceExpression
    Выражение1
    Выражение2
    anAlternationExpression
    Альтернатива1
    Альтернатива2
    aLiteralExpression
    'raining'
    aLiteralExpression
    'dogs'
    aLiteralExpression
    'cats'
    aRepetitionExpression
    Повторить

    Паттерн Interpreter (интерпретатор)
    289
    представляет выражение raining & (dogs | cats) *
    Чтобы создать интерпретатор регулярных выражений, можно определить в каждом подклассе
    RegularExpression операцию
    Interpret
    , получающую в аргументе контекст, в котором должно интерпретироваться выражение.
    Контекст состоит из входной строки и информации о текущем состоянии по- иска совпадения. В каждом подклассе
    RegularExpression операция
    Interpret ищет совпадение следующей части входной строки с учетом текущего кон- текста. Например:
    „
    „
    LiteralExpression проверяет, соответствует ли входная строка литера- лу, который хранится в объекте подкласса;
    „
    „
    AlternationExpression проверяет, соответствует ли строка одной из альтернатив;
    „
    „
    RepetitionExpression проверяет, имеются ли в входной строке повторя- ющиеся соответствия выражения;
    и так далее.
    Применимость
    Используйте паттерн интерпретатор в ситуациях, когда имеется интерпрети- руемый язык, конструкции которого можно представить в виде абстрактных синтаксических деревьев. Этот паттерн лучше всего работает в следующих случаях:
    „
    „
    грамматика проста. Для сложных грамматик иерархия классов стано- вится слишком громоздкой и неуправляемой. В таких случаях лучше применять парсеры-генераторы, поскольку они могут интерпретиро- вать выражения без построения абстрактных синтаксических деревьев, что экономит память (а возможно, и время);
    „
    „
    эффективность не является главным критерием. Наиболее эффектив- ные интерпретаторы обычно не работают непосредственно с деревья- ми, а сначала транслируют их в другую форму. Так, регулярное выра- жение часто преобразуется в конечный автомат. Но даже в этом случае сам транслятор можно реализовать с помощью паттерна интерпре- татор.

    290
    Глава 5. Паттерны поведения
    Структура
    NonterminalExpression
    Interpret(Context)
    Client
    Context
    AbstractExpression
    Interpret(Context)
    TerminalExpression
    Interpret(Context)
    Участники
    „
    „
    AbstractExpression (
    RegularExpression
    ) — абстрактное выражение:
    • объявляет абстрактную операцию
    Interpret
    , общую для всех узлов в абстрактном синтаксическом дереве;
    „
    „
    TerminalExpression (
    LiteralExpression
    ) — терминальное выражение:
    • реализует операцию
    Interpret для терминальных символов грамма- тики;
    • необходим отдельный экземпляр для каждого терминального символа в предложении;
    „
    „
    NonterminalExpression (
    AlternationExpression
    ,
    RepetitionExpression
    ,
    SequenceExpressions
    ) — нетерминальное выражение:
    • по одному такому классу требуется для каждого грамматического правила R :: = R
    1
    R
    2
    ...R
    n
    ;
    • хранит переменные экземпляра типа
    AbstractExpression для каждого символа от R
    1
    до R
    n
    ;
    • реализует операцию
    Interpret для нетерминальных символов грам- матики. Эта операция рекурсивно вызывает себя же для переменных, представляющих R
    1
    , ... R
    n
    ;
    „
    „
    Context — контекст:
    • содержит информацию, глобальную по отношению к интерпрета- тору;

    Паттерн Interpreter (интерпретатор)
    291
    „
    „
    Client — клиент:
    • строит (или получает в готовом виде) абстрактное синтаксическое дерево, представляющее отдельное предложение на языке с дан- ной грамматикой. Дерево собирается из экземпляров классов
    NonterminalExpression и
    TerminalExpression
    ;
    • вызывает операцию
    Interpret
    Отношения
    „
    „
    клиент строит (или получает в готовом виде) конструкцию в виде аб- страктного синтаксического дерева, в узлах которого находятся объек- ты классов
    NonterminalExpression и
    TerminalExpression
    . Затем клиент инициализирует контекст и вызывает операцию
    Interpret
    ;
    „
    „
    в каждом узле вида
    NonterminalExpression через операции
    Interpret определяется операция
    Interpret для каждого подвыражения. Для класса
    TerminalExpression операция
    Interpret определяет базу рекур- сии;
    „
    „
    операции
    Interpret в каждом узле используют контекст для сохране- ния и доступа к состоянию интерпретатора.
    Результаты
    Основные достоинства и недостатки паттерна интерпретатор:
    „
    „
    простота изменения и расширения грамматики. Поскольку для пред- ставления грамматических правил в паттерне используются классы, то для изменения или расширения грамматики можно применять наследо- вание. Существующие выражения можно модифицировать постепенно, а новые определять как вариации старых;
    „
    „
    простая реализация грамматики. Реализации классов, описывающих узлы абстрактного синтаксического дерева, похожи. Такие классы легко программируются, а зачастую они могут автоматически генерироваться генератором компиляторов или парсером-генератором;
    „
    „
    сложность сопровождения сложных грамматик. В паттерне интерпре- татор определяется по меньшей мере один класс для каждого правила грамматики (для правил, определенных с помощью формы Бэкуса —
    Наура — BNF, может понадобиться и более одного класса). Поэтому сопровождение грамматики с большим числом правил иногда оказы- вается трудной задачей. Для ее решения могут быть применены другие паттерны (см. раздел «Реализация»). Но если грамматика очень сложна,

    292
    Глава 5. Паттерны поведения лучше прибегнуть к другим методам, например воспользоваться генера- тором компиляторов или парсером-генератором;
    „
    „
    добавление новых способов интерпретации выражений. Паттерн интер- претатор позволяет легко изменить способ вычисления выражений. На- пример, реализовать красивую печать выражения вместо проверки вхо- дящих в него типов можно, просто определив новую операцию в классах выражений. Если вам приходится часто создавать новые способы ин- терпретации выражений, подумайте о применении паттерна посети- тель (379). Это поможет избежать изменения классов, описывающих грамматику.
    Реализация
    У реализаций паттернов интерпретатор и компоновщик (196) много общего.
    Следующие аспекты относятся только к интерпретатору:
    „
    „
    создание абстрактного синтаксического дерева. Паттерн интерпретатор не поясняет, как создавать дерево, то есть разбор выражения не вхо- дит в его задачу. Абстрактное дерево разбора можно строить таблично- управляемым или написанным вручную парсером (обычно методом ре- курсивного спуска), а также самим клиентом;
    „
    „
    определение операции Interpret. Определять операцию
    Interpret в клас- сах выражений необязательно. Если создавать новые интерпретаторы приходится часто, то лучше воспользоваться паттерном посетитель и по- местить операцию
    Interpret в отдельный объект-посетитель. Напри- мер, для грамматики языка программирования будет нужно определить много операций над абстрактными синтаксическими деревьями: про- верку типов, оптимизацию, генерацию кода и т. д. Лучше, конечно, ис- пользовать посетителя и не определять эти операции в каждом классе грамматики;
    „
    „
    разделение терминальных символов с помощью паттерна «приспособле-
    нец». Для грамматик, предложения которых содержат много вхождений одного и того же терминального символа, может оказаться полезным разделение этого символа. Хорошим примером служат грамматики компьютерных программ, поскольку в них каждая переменная встре- чается в коде многократно. В примере из раздела «Мотивация» терми- нальный символ dog (для моделирования которого используется класс
    LiteralExpression
    ) может встречаться многократно.
    В терминальных узлах обычно не хранится информация о положении в абстрактном синтаксическом дереве. Необходимый для интерпретации

    Паттерн Interpreter (интерпретатор)
    293
    контекст предоставляют им родительские узлы. Налицо различие между разделяемым (внутренним) и передаваемым (внешним) состояниями, так что вполне применим паттерн приспособленец (231).
    Например, каждый экземпляр класса
    LiteralExpression для dog полу- чает контекст, состоящий из уже просмотренной части строки. И каждый такой экземпляр делает в своей операции
    Interpret одно и то же — про- веряет, содержит ли остаток входной строки слово dog, — независимо от того, в каком месте дерева этот экземпляр встречается.
    Пример кода
    Ниже приведены два примера. Первый — законченная программа на Smalltalk для проверки того, существует ли в заданной последовательности совпаде- ние регулярного выражения. Второй — программа на C++ для вычисления булевых выражений.
    Программа сопоставления с регулярным выражением проверяет, является ли строка корректным предложением языка, определяемого этим выражением.
    Регулярное выражение определено следующей грамматикой:
    expression ::= literal | alternation | sequence | repetition |
    '(' expression ')'
    alternation ::= expression '|' expression sequence ::= expression '&' expression repetition ::= expression 'repeat'
    literal ::= 'a' | 'b' | 'c' | ... { 'a' | 'b' | 'c' | ... }*
    Между этой грамматикой и той, что приведена в разделе «Мотивация», есть небольшие отличия. Мы слегка изменили синтаксис регулярных выраже- ний, поскольку в Smalltalk символ
    *
    не может быть постфиксной операцией, поэтому вместо него употребляется слово repeat
    . Например, регулярное выражение
    (('dog ‘ | ‘cat ‘) repeat & ‘weather’) соответствует входной строке 'dog dog cat weather’
    Для реализации программы сопоставления мы определим пять классов, упомянутых на с. 379. В классе
    SequenceExpression есть переменные экземпляра expression1
    и expression2
    для хранения ссылок на потомков в дереве. Класс
    AlternationExpression хранит альтернативы в переменных экземпляра alternative1
    и alternative2
    , а класс
    RepetitionExpression
    — повторяемое выражение в переменной экземпляра repetition
    . В классе

    1   ...   20   21   22   23   24   25   26   27   ...   38


    написать администратору сайта