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

  • CreationTool SelectionTool TextTool

  • Compositor

  • ConcreteStrategyA AlgorithmInterface()ConcreteStrategyB AlgorithmInterface() Strategy

  • ConcreteStrategy

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


    Скачать 6.37 Mb.
    НазваниеЭ. Гамма, Р. Хелм
    АнкорFactorial
    Дата14.03.2022
    Размер6.37 Mb.
    Формат файлаpdf
    Имя файлаPatterny_Obektno-Orientirovannogo_Proektirovania_2020.pdf
    ТипДокументы
    #395452
    страница31 из 38
    1   ...   27   28   29   30   31   32   33   34   ...   38
    356
    Глава 5. Паттерны поведения
    „
    „
    возможность совместного использования объектов состояния. Если в объекте состояния
    State отсутствуют переменные экземпляра, то есть представляемое им состояние кодируется исключительно самим типом, то разные контексты могут разделять один и тот же объект
    State
    . Когда состояния разделяются таким образом, они являются, по сути дела, при- способленцами (см. описание паттерна приспособленец (231)), у кото- рых нет внутреннего состояния, а есть только поведение.
    Реализация
    При реализации паттерна состояние следует обратить внимание на следу- ющие аспекты:
    „
    „
    что определяет переходы между состояниями. Паттерн состояние ничего не сообщает о том, какой участник определяет критерий перехода между состояниями. Если критерии зафиксированы, то их можно реализовать непосредственно в классе
    Context
    . Однако в общем случае более гибкий и правильный подход заключается в том, чтобы позволить самим под- классам класса State определять следующее состояние и момент пере- хода. Для этого в класс Context надо добавить интерфейс, позволяющий объектам
    State установить состояние контекста. Такую децентрализо- ванную логику переходов проще модифицировать и расширять — нужно лишь определить новые подклассы
    State
    . Недостаток децентрализации в том, что каждый подкласс
    State должен знать еще хотя бы об одном подклассе, что вносит реализационные зависимости между подклассами;
    „
    „
    табличная альтернатива. Том Каргилл (Tom Cargill) в книге C++
    Programming Style [Car92] описывает другой способ структурирования кода, управляемого состояниями. Он использует таблицу для отобра- жения входных данных на переходы между состояниями. С ее помощью можно определить, в какое состояние нужно перейти при поступлении некоторых входных данных. По существу, тем самым мы заменяем ус- ловный код (или виртуальные функции, если речь идет о паттерне со- стояние) поиском в таблице. Основное преимущество таблиц — в их регулярности: для изменения критериев перехода достаточно модифи- цировать только данные, а не код. Но есть и недостатки:
    • поиск в таблице часто менее эффективен, чем вызов функции (вир- туальной);
    • представление логики переходов в однородном табличном формате делает критерии менее явными и, стало быть, усложняет их понимание;
    • обычно трудно добавить действия, которыми сопровождаются переходы между состояниями. Табличный метод учитывает состояния и переходы

    Паттерн State (состояние)
    357
    между ними, но его необходимо дополнить, чтобы при каждом изме- нении состояния можно было выполнять произвольные вычисления.
    Главное различие между конечными автоматами на базе таблиц и паттер- ном состояние можно сформулировать так: паттерн состояние моделирует поведение, зависящее от состояния, а табличный метод акцентирует внимание на определении переходов между состояниями;
    „
    „
    создание и уничтожение объектов состояния. В процессе разработки обычно приходится выбирать между: (1) созданием объектов состоя- ния, когда в них возникает необходимость, и уничтожением сразу после использования, и (2) созданием их заранее и навсегда.
    Первый вариант предпочтителен в тех случаях, когда возможные состояния системы неизвестны заранее, а контекст изменяет состояние сравнитель- но редко. При этом объекты, которые никогда не будут использованы, не создаются, что может быть существенно, если в объектах состояния хра- нится много информации. Если изменения состояния происходят часто, и уничтожать представляющие их объекты было бы нежелательно (ибо они могут очень скоро понадобиться вновь), лучше воспользоваться вторым подходом. Время на создание объектов затрачивается только один раз, в самом начале, а на уничтожение — не затрачивается вовсе. Правда, этот вариант может оказаться неудобным, так как в контексте должны храниться ссылки на все состояния, в которых теоретически может оказаться система;
    „
    „
    использование динамического наследования. Изменение поведения по кон- кретному запросу может достигаться сменой класса объекта во время вы- полнения, но в большинстве объектно-ориентированных языков такая воз- можность не поддерживается. Исключение составляет Self [US87] и другие основанные на делегировании языки, которые предоставляют такой меха- низм и, следовательно, поддерживают паттерн состояние напрямую. Объ- екты в Self могут делегировать операции другим объектам, обеспечивая тем самым некую форму динамического наследования. Изменение целевого объекта делегирования во время выполнения фактически приводит к из- менению структуры графа наследования. Такой механизм позволяет объ- ектам варьировать поведение путем изменения своего класса.
    Пример кода
    В следующем примере приведен код на языке C++ с TCP-соединением из раз- дела «Мотивация». Это упрощенный вариант протокола TCP, в нем, конечно же, представлен не весь протокол и даже не все состояния TCP-соединений
    1 1
    Пример основан на описании протокола установления TCP-соединений из книги
    Линча и Роуза [LR93].

    358
    Глава 5. Паттерны поведения
    Прежде всего определим класс
    TCPConnection
    , который предоставляет интер- фейс для передачи данных и обрабатывает запросы на изменение состояния:
    class TCPOctetStream;
    class TCPState;
    class TCPConnection {
    public:
    TCPConnection();
    void ActiveOpen();
    void PassiveOpen();
    void Close();
    void Send();
    void Acknowledge();
    void Synchronize();
    void ProcessOctet(TCPOctetStream*);
    private:
    friend class TCPState;
    void ChangeState(TCPState*);
    private:
    TCPState* _state;
    };
    В переменной
    _state класса
    TCPConnection хранится экземпляр класса
    TCPState
    . Этот класс дублирует интерфейс изменения состояния, определен- ный в классе
    TCPConnection
    . Каждая операция
    TCPState получает экземпляр
    TCPConnection в параметре, что позволяет объекту
    TCPState получить доступ к данным объекта
    TCPConnection и изменить состояние соединения:
    class TCPState {
    public:
    virtual void Transmit(TCPConnection*, TCPOctetStream*);
    virtual void ActiveOpen(TCPConnection*);
    virtual void PassiveOpen(TCPConnection*);
    virtual void Close(TCPConnection*);
    virtual void Synchronize(TCPConnection*);
    virtual void Acknowledge(TCPConnection*);
    virtual void Send(TCPConnection*);
    protected: void ChangeState(TCPConnection*, TCPState*);
    };
    TCPConnection делегирует все запросы, зависящие от состояния, хранимому в
    _state экземпляру
    TCPState
    . Кроме того, в классе
    TCPConnection существует операция, с помощью которой в эту переменную можно записать указатель

    Паттерн State (состояние)
    359
    на другой объект
    TCPState
    . Конструктор класса
    TCPConnection инициализи- рует
    _state указателем на состояние
    TCPClosed
    (оно будет определено ниже):
    TCPConnection::TCPConnection () {
    _state = TCPClosed::Instance();
    }
    void TCPConnection::ChangeState (TCPState* s) {
    _state = s;
    }
    void TCPConnection::ActiveOpen () {
    _state->ActiveOpen(this);
    }
    void TCPConnection::PassiveOpen () {
    _state->PassiveOpen(this);
    }
    void TCPConnection::Close () {
    _state->Close(this);
    }
    void TCPConnection::Acknowledge () {
    _state->Acknowledge(this);
    }
    void TCPConnection::Synchronize () {
    _state->Synchronize(this);
    }
    В классе
    TCPState реализовано поведение по умолчанию для всех деле- гированных ему запросов. Он может также изменить состояние объекта
    TCPConnection посредством операции
    ChangeState
    TCPState объявляется другом класса
    TCPConnection
    , что дает ему привилегированный доступ к этой операции:
    void TCPState::Transmit (TCPConnection*, TCPOctetStream*) { }
    void TCPState::ActiveOpen (TCPConnection*) { }
    void TCPState::PassiveOpen (TCPConnection*) { }
    void TCPState::Close (TCPConnection*) { }
    void TCPState::Synchronize (TCPConnection*) { }
    void TCPState::ChangeState (TCPConnection* t, TCPState* s) {
    t->ChangeState(s);
    }

    360
    Глава 5. Паттерны поведения
    В подклассах
    TCPState реализовано поведение, зависящее от состояния.
    Соединение TCP может находиться во многих состояниях:
    Established
    (установлено),
    Listening
    (прослушивание),
    Closed
    (закрыто) и т. д., и для каждого из них есть свой подкласс
    TCPState
    . Мы подробно рассмотрим три подкласса:
    TCPEstablished
    ,
    TCPListen и
    TCPClosed
    :
    class TCPEstablished : public TCPState {
    public:
    static TCPState* Instance();
    virtual void Transmit(TCPConnection*, TCPOctetStream*);
    virtual void Close(TCPConnection*);
    };
    class TCPListen : public TCPState {
    public:
    static TCPState* Instance();
    virtual void Send(TCPConnection*);
    // ...
    };
    class TCPClosed : public TCPState {
    public:
    static TCPState* Instance();
    virtual void ActiveOpen(TCPConnection*);
    virtual void PassiveOpen(TCPConnection*);
    // ...
    };
    В подклассах
    TCPState нет никакого локального состояния, поэтому они могут использоваться совместно, так что потребуется только по одному экземпляру каждого класса. Уникальный экземпляр подкласса
    TCPState создается обращением к статической операции
    Instance
    1
    В подклассах
    TCPState реализовано зависящее от состояния поведение для тех запросов, которые допустимы в этом состоянии:
    void TCPClosed::ActiveOpen (TCPConnection* t) {
    // Послать SYN, получить SYN, ACK и т. д.
    ChangeState(t, TCPEstablished::Instance());
    }
    void TCPClosed::PassiveOpen (TCPConnection* t) {
    ChangeState(t, TCPListen::Instance());
    }
    1
    Таким образом, каждый подкласс TCPState — это одиночка.

    Паттерн State (состояние)
    361
    void TCPEstablished::Close (TCPConnection* t) {
    // Послать FIN, получить ACK для FIN
    ChangeState(t, TCPListen::Instance());
    }
    void TCPEstablished::Transmit ( TCPConnection* t, TCPOctetStream* o ) {
    t->ProcessOctet(o);
    }
    void TCPListen::Send (TCPConnection* t) {
    // Послать SYN, получить SYN, ACK и т. д.
    ChangeState(t, TCPEstablished::Instance());
    }
    После выполнения действий, специфичных для своего состояния, эти опера- ции вызывают
    ChangeState для изменения состояния объекта
    TCPConnection
    У него нет никакой информации о протоколе TCP. Именно подклассы
    TCPState определяют переходы между состояниями и действия, диктуемые протоколом.
    Известные применения
    Ральф Джонсон и Джонатан Цвейг [JZ91] характеризуют паттерн состояние и описывают его применительно к протоколу TCP.
    Наиболее популярные интерактивные программы рисования предоставляют
    «инструменты» для наглядного выполнения операций на экране. Например, инструмент для рисования линий позволяет пользователю щелкнуть в про- извольной точке мышью, а затем, перемещая мышь, провести из этой точки линию. Инструмент выбора позволяет выбирать некоторые фигуры. Обычно все имеющиеся инструменты размещаются в палитре. Задача пользователя заключается в том, чтобы правильно выбрать и применить инструмент, но на самом деле поведение редактора изменяется при смене инструмента: при помощи инструмента для рисования мы создаем фигуры, при помощи инструмента выбора — выбираем их и т. д.
    Чтобы отразить зависимость поведения редактора от текущего инструмента, можно воспользоваться паттерном состояние.
    Можно определить абстрактный класс
    Tool
    , подклассы которого реализуют поведение, зависящее от инструмента. Графический редактор хранит ссылку на текущий объект Tool и делегирует ему поступающие запросы. При выборе инструмента редактор использует другой объект, что приводит к изменению поведения.
    Этот прием используется в каркасах графических редакторов HotDraw
    [Joh92] и Unidraw [VL90]. Он позволяет клиентам легко определять новые

    362
    Глава 5. Паттерны поведения виды инструментов. В HotDraw класс
    DrawingController переадресует запросы текущему объекту
    Tool
    . В Unidraw соответствующие классы на- зываются
    Viewer и
    Tool
    . На приведенной ниже схеме классов схематично представлены интерфейсы классов
    Tool и
    DrawingController
    CreationTool
    SelectionTool
    TextTool
    HandleMousePress()
    HandleMouseRelease()
    HandleCharacter()
    GetCursor()
    Activate()
    Tool
    MousePressed()
    ProcessKeyboard()
    Initialize()
    DrawingController
    currentTool
    Описанная Джеймсом Коплиеном [Cop92] идиома «конверт — письмо»
    (Envelope-Letter) также имеет отношение к паттерну состояние. По сути она представляет собой механизм изменения класса объекта во время выполне- ния. Паттерн состояние более конкретен; в нем акцент делается на работу с объектами, поведение которых зависит от состояния.
    Родственные паттерны
    Паттерн приспособленец (231) объясняет, как и когда можно совместно использовать объекты состояния.
    Объекты состояния часто бывают одиночками (157).
    ПАТТЕРН STRATEGY (СТРАТЕГИЯ)
    Название и классификация паттерна
    Стратегия — паттерн поведения объектов.
    Назначение
    Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Стратегия позволяет изменять алгоритмы незави- симо от клиентов, которые ими пользуются.

    Паттерн Strategy (стратегия)
    363
    Другие названия
    Policy (политика).
    Мотивация
    Существует много алгоритмов для разбиения текста на строки. Жестко
    «зашивать» все подобные алгоритмы в классы, которые в них нуждаются, нежелательно по нескольким причинам:
    „
    „
    клиент, которому требуется алгоритм разбиения на строки, усложня- ется при включении в него соответствующего кода. Таким образом, клиенты становятся более громоздкими и создают больше сложностей в сопровождении, особенно если нужно поддерживать сразу несколько алгоритмов;
    „
    „
    в зависимости от обстоятельств могут применяться разные алгоритмы.
    Было бы неэффективно поддерживать несколько алгоритмов разбие- ния на строки, если мы не будем ими пользоваться;
    „
    „
    если разбиение на строки является неотъемлемой частью клиента, то задачи добавления новых и модификации существующих алгоритмов усложняются.
    Всех этих проблем можно избежать, если определить классы, инкапсули- рующие различные алгоритмы разбиения на строки. Инкапсулированный таким образом алгоритм называется стратегией.
    Compositor
    Compose()
    SimpleCompositor
    Compose()
    ArrayCompositor
    Compose()
    TeXCompositor
    Compose()
    Composition
    Traverse()
    Repair()
    compositor–>Compose compositor
    Допустим, класс
    Composition отвечает за разбиение на строки текста, ото- бражаемого в окне программы просмотра, и его своевременное обновление.
    Стратегии разбиения на строки определяются не в классе
    Composition
    , а в подклассах абстрактного класса
    Compositor
    . Несколько примеров:
    „
    „
    SimpleCompositor реализует простую стратегию, выделяющую по одной строке за раз;

    364
    Глава 5. Паттерны поведения
    „
    „
    TeXCompositor реализует алгоритм поиска точек разбиения на строки, принятый в редакторе TeX. Эта стратегия пытается оптимизировать разбиение на строки глобально, то есть в целом абзаце;
    „
    „
    ArrayCompositor реализует стратегию расстановки переходов на новую строку таким образом, что в каждой строке оказывается одно и то же число элементов. Например, это может быть полезно при построчном отображении набора пиктограмм.
    Объект
    Composition хранит ссылку на объект
    Compositor
    . Всякий раз, когда объекту
    Composition требуется переформатировать текст, он делегирует дан- ную обязанность своему объекту
    Compositor
    . Чтобы указать, какой объект
    Compositor должен использоваться, клиент встраивает его в объект
    Composition
    Применимость
    Основные условия для применения паттерна стратегия:
    „
    „
    наличие множества родственных классов, отличающихся только поведе-
    нием. Стратегия позволяет настроить класс одним из многих возмож- ных вариантов поведения;
    „
    „
    наличие нескольких разновидностей алгоритма. Например, можно опре- делить два варианта алгоритма, один из которых требует больше време- ни, а другой — больше памяти. Стратегии разрешается применять, когда варианты алгоритмов реализованы в виде иерархии классов [HO87];
    „
    „
    в алгоритме содержатся данные, о которых клиент не должен «знать».
    Используйте паттерн стратегия, чтобы не раскрывать сложные, специ- фичные для алгоритма структуры данных;
    „
    „
    в классе определено много вариантов поведения, представленных разветв-
    ленными условными операторами. В этом случае проще перенести код из ветвей в отдельные классы стратегий.
    Структура strategy
    ConcreteStrategyA_AlgorithmInterface()ConcreteStrategyB_AlgorithmInterface()_Strategy'>ConcreteStrategyA
    AlgorithmInterface()
    ConcreteStrategyB
    AlgorithmInterface()
    Strategy
    AlgorithmInterface()
    Context
    ContextInterface()
    ConcreteStrategyC
    AlgorithmInterface()

    Паттерн Strategy (стратегия)
    365
    Участники
    „
    „
    Strategy (
    Compositor
    ) — стратегия:
    • объявляет общий для всех поддерживаемых алгоритмов интерфейс.
    Класс
    Context пользуется этим интерфейсом для вызова конкретного алгоритма, определенного в классе
    ConcreteStrategy
    ;
    „
    „
    ConcreteStrategy (
    SimpleCompositor
    ,
    TeXCompositor
    ,
    ArrayCompositor
    ) — конкретная стратегия:
    • реализует алгоритм, использующий интерфейс, объявленный в классе
    Strategy
    ;
    „
    „
    Context (
    Composition
    ) — контекст:
    • настраивается объектом класса
    ConcreteStrategy
    ;
    • хранит ссылку на объект класса
    Strategy
    ;
    • может определять интерфейс, который позволяет объекту
    Strategy обращаться к данным контекста.
    Отношения
    „
    „
    Классы
    Strategy и
    Context взаимодействуют для реализации выбран- ного алгоритма. Контекст может передать стратегии все необходимые алгоритму данные в момент его вызова. Вместо этого контекст может позволить обращаться к своим операциям в нужные моменты, переда- вая ссылку на самого себя операциям класса
    Strategy
    ;
    „
    „
    контекст переадресует запросы своих клиентов объекту-стратегии.
    Обычно клиент создает объект
    ConcreteStrategy и передает его кон- тексту, после чего клиент взаимодействует исключительно с контек- стом. Часто в распоряжении клиента находится несколько классов
    ConcreteStrategy
    , которые он может выбирать.
    Результаты
    Основные достоинства и недостатки паттерна стратегия:
    „
    „
    семейства родственных алгоритмов. Иерархия классов
    Strategy опре- деляет семейство алгоритмов или вариантов поведения, которые можно повторно использовать в разных контекстах. Наследование позволяет вычленить общую для всех алгоритмов функциональность;
    „
    „
    альтернатива порождению подклассов. Наследование поддерживает многообразие алгоритмов или поведений. Можно напрямую породить от
    Context подклассы с различными поведениями. Но при этом поведение

    1   ...   27   28   29   30   31   32   33   34   ...   38


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