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

  • DAGChangeManager

  • TCPState

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


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

    346
    Глава 5. Паттерны поведения
    Этой ловушки можно избежать, отправляя уведомления из шаблонных методов (см. описание паттерна шаблонный метод (373)) абстрактно- го класса
    Subject
    . Определите примитивную операцию, замещаемую в подклассах, и обратитесь к
    Notify
    , используя последнюю операцию в шаблонном методе. В таком случае существует гарантия, что со- стояние объекта непротиворечиво, если операции
    Subject замещены в подклассах:
    void Text::Cut (TextRange r) {
    ReplaceRange(r); // Переопределяется в подклассах
    Notify();
    }
    Кстати, всегда желательно фиксировать, какие операции класса
    Subject инициируют обновления;
    „
    „
    предотвращение зависимости протокола обновления от наблюдате-
    ля: модели вытягивания и проталкивания. В реализациях паттерна наблюдатель субъект довольно часто транслирует всем подписчикам дополнительную информацию о характере изменения. Она передает- ся в виде аргумента операции
    Update
    , и объем ее меняется в широких диапазонах.
    На одном полюсе находится так называемая модель проталкивания (push model), когда субъект посылает наблюдателям детальную информацию об изменении независимо от того, нужно ли им это. На другом — модель
    вытягивания (pull model), когда субъект не посылает ничего, кроме ми- нимального уведомления, а наблюдатели запрашивают детали позднее.
    Модель вытягивания подчеркивает неинформированность субъекта о сво- их наблюдателях, а в модели проталкивания предполагается, что субъект владеет определенной информацией о потребностях наблюдателей. В слу- чае применения модели проталкивания степень повторного их использо- вания может снизиться, так как классы
    Subject делают предположения о классах
    Observer
    , которые могут оказаться неправильными. С другой стороны, модель вытягивания может оказаться неэффективной, ибо на- блюдателям без помощи субъекта необходимо выяснять, что изменилось;
    „
    „
    явное определение модификаций, представляющих интерес.Эффектив- ность обновления можно повысить, расширив интерфейс регистрации субъекта, то есть предоставив возможность при регистрации наблю- дателя указать, какие именно события его интересуют. Когда событие происходит, субъект информирует лишь тех наблюдателей, которые

    Паттерн Observer (наблюдатель)
    347
    про явили к нему интерес. Чтобы получать конкретное событие, наблю- датели присоединяются к своим субъектам следующим образом:
    void Subject::Attach(Observer*, Aspect& interest);
    где interest определяет представляющее интерес событие. В момент отправки уведомления субъект передает своим наблюдателям изменив- шийся аспект в виде параметра операции
    Update
    . Например:
    void Observer::Update(Subject*, Aspect& interest);
    „
    „
    инкапсуляция сложной семантики обновления. Если отношения за- висимости между субъектами и наблюдателями становятся особенно сложными, то может потребоваться объект, инкапсулирующий эти от- ношения. Будем называть его
    ChangeManager
    (менеджер изменений). Он должен свести к минимуму объем работы, необходимой для того, чтобы наблюдатели смогли отразить изменения субъекта. Например, если не- которая операция влечет за собой изменения в нескольких независимых субъектах, то хотелось бы, чтобы наблюдатели уведомлялись после того, как будут модифицированы все субъекты, дабы не ставить в известность одного и того же наблюдателя несколько раз.
    У класса
    ChangeManager есть три обязанности:
    • строить отображение между субъектом и его наблюдателями и предо- ставлять интерфейс для поддержания отображения в актуальном состоянии. Это освобождает субъектов от необходимости хранить ссылки на своих наблюдателей и наоборот;
    определять конкретную стратегию обновления;
    • обновлять всех зависимых наблюдателей по запросу от субъекта.
    На следующей схеме представлена простая реализация паттерна наблюда- тель с использованием менеджера изменений
    ChangeManager
    . Имеется два специализированных менеджера.
    SimpleChangeManager всегда обновляет всех наблюдателей каждого субъекта, а
    DAGChangeManager обрабатывает направленные ациклические графы зависимостей между субъектами и их наблюдателями. Когда наблюдатель должен «присматривать» за не- сколькими субъектами, предпочтительнее использовать
    DAGChangeManager
    В этом случае изменение сразу двух или более субъектов может привести к избыточным обновлениям. Объект
    DAGChangeManager гарантирует, что наблюдатель в любом случае получит только одно уведомление. Если об- новление одного и того же наблюдателя допускается несколько раз подряд, то вполне достаточно объекта
    SimpleChangeManager

    348
    Глава 5. Паттерны поведения
    Observer
    Update(Subject)
    Register(Subject, Observer)
    Unregister(Subject, Observer)
    Notify()
    ChangeManager
    Subject–Observer mapping
    Register(Subject, Observer)
    Unregister(Subject, Observer)
    Notify()
    SimpleChangeManager
    Register(Subject, Observer)
    Unregister(Subject, Observer)
    Notify()
    DAGChangeManager
    Пометить всех наблюдателей для обновления
    Обновить всех помеченных наблюдателей
    Для всех субъектов s
    Для всех наблюдателей о в s o–>Update(s)
    chman–>Register(this,o)
    chman–>Notify()
    Attach(Observer o)
    Detach(Observer)
    Notify()
    Subject
    subjects chman observers
    ChangeManager
    — это пример паттерна посредник (319). В общем случае есть только один объект
    ChangeManager
    , известный всем участникам. По- этому полезен будет также и паттерн одиночка (157);
    „
    „
    комбинирование классов Subject и Observer. В библиотеках классов, кото- рые написаны на языках, не поддерживающих множественного насле- дования (например, на Smalltalk), обычно не определяются отдельные классы
    Subject и
    Observer
    . Их интерфейсы комбинируются в одном классе. Это позволяет определить объект, выступающий в роли одно- временно субъекта и наблюдателя, без множественного наследования.
    Так, в Smalltalk интерфейсы
    Subject и
    Observer определены в корневом классе
    Object и потому доступны вообще всем классам.
    Пример кода
    Интерфейс наблюдателя определен в абстрактном классе
    Observer
    :
    class Subject;
    class Observer {
    public:
    virtual

    Observer();
    virtual void Update(Subject* theChangedSubject) = 0;
    protected:
    Observer();
    };

    Паттерн Observer (наблюдатель)
    349
    Такая реализация поддерживает несколько субъектов для одного наблюдате- ля. Передача субъекта в параметре операции
    Update позволяет наблюдателю определить, какой из наблюдаемых им субъектов изменился.
    Аналогичным образом в абстрактном классе
    Subject определен интерфейс субъекта:
    class Subject {
    public:
    virtual Subject();
    virtual void Attach(Observer*);
    virtual void Detach(Observer*);
    virtual void Notify();
    protected:
    Subject();
    private:
    List *_observers;
    };
    void Subject::Attach (Observer* o) {
    _observers->Append(o);
    }
    void Subject::Detach (Observer* o) {
    _observers->Remove(o);
    }
    void Subject::Notify () {
    ListIterator i(_observers);
    for (i.First(); !i.IsDone(); i.Next()) {
    i.CurrentItem()->Update(this);
    }
    }
    ClockTimer
    — это конкретный субъект, который следит за временем суток. Он оповещает наблюдателей каждую секунду. Класс
    ClockTimer предоставляет интерфейс для получения отдельных компонентов времени: часа, минуты, секунды и т. д.:
    class ClockTimer : public Subject {
    public:
    ClockTimer();
    virtual int GetHour();
    virtual int GetMinute();
    virtual int GetSecond();
    void Tick();
    };

    350
    Глава 5. Паттерны поведения
    Операция
    Tick вызывается через одинаковые интервалы внутренним тай- мером. Тем самым обеспечивается правильный отсчет времени. При этом обновляется внутреннее состояние объекта
    ClockTimer и вызывается опе- рация
    Notify для извещения наблюдателей об изменении:
    void ClockTimer::Tick () {
    // Обновить внутреннее представление времени
    // ...
    Notify();
    }
    Теперь можно определить класс
    DigitalClock для вывода времени. Свою графическую функциональность он наследует от класса
    Widget
    , предо- ставляемого библиотекой для построения пользовательских интерфейсов.
    Интерфейс наблюдателя примешивается к интерфейсу
    DigitalClock путем наследования от класса
    Observer
    :
    class DigitalClock: public Widget, public Observer {
    public:
    DigitalClock(ClockTimer*);
    virtual DigitalClock();
    virtual void Update(Subject*);
    // Замещает операцию класса Observer virtual void Draw();
    // Замещает операцию класса Widget;
    // определяет способ отображения часов private:
    ClockTimer* _subject;
    };
    DigitalClock::DigitalClock (ClockTimer* s) {
    _subject = s;
    _subject->Attach(this);
    }
    DigitalClock:: DigitalClock () {
    _subject->Detach(this);
    }
    Прежде чем начнется рисование часов посредством операции
    Update
    , будет проверено, что уведомление получено именно от объекта таймера:
    void DigitalClock::Update (Subject* theChangedSubject) {
    if (theChangedSubject == _subject) {
    Draw();

    Паттерн Observer (наблюдатель)
    351
    }
    }
    void DigitalClock::Draw () {
    // Получить новые значения от субъекта int hour = _subject->GetHour();
    int minute = _subject->GetMinute();
    // etc.
    // Нарисовать цифровые часы
    }
    Аналогичным образом определяется класс
    AnalogClock
    :
    class AnalogClock : public Widget, public Observer {
    public:
    AnalogClock(ClockTimer*);
    virtual void Update(Subject*);
    virtual void Draw();
    // ...
    };
    Следующий код создает объекты классов
    AnalogClock и
    DigitalClock
    , кото- рые всегда показывают одно и то же время:
    ClockTimer* timer = new ClockTimer;
    AnalogClock* analogClock = new AnalogClock(timer);
    DigitalClock* digitalClock = new DigitalClock(timer);
    При каждом срабатывании таймера timer оба экземпляра часов обновляются и перерисовывают себя.
    Известные применения
    Первый и, возможно, самый известный пример паттерна наблюдатель поя- вился в схеме «модель/представление/контроллер» (MVC) языка Smalltalk, которая представляет собой каркас для построения пользовательских ин- терфейсов в среде Smalltalk [KP88]. Класс
    Model в MVC — субъект, а
    View
    — базовый класс для наблюдателей. В языках Smalltalk, ET++ [WGM88] и библиотеке классов THINK [Sym93b] предлагается общий механизм зависимостей, в котором интерфейсы субъекта и наблюдателя помещены в класс, являющийся общим родителем всех остальных системных классов.
    Среди других библиотек для построения интерфейсов пользователя, в кото- рых используется паттерн наблюдатель, стоит упомянуть InterViews [LVC89],

    352
    Глава 5. Паттерны поведения
    Andrew Toolkit [P+88] и Unidraw [VL90]. В InterViews явно определены классы
    Observer и
    Observable
    (для субъектов). В библиотеке Andrew они называются представлением (view) и объектом данных (data object) соот- ветственно. Unidraw делит объекты графического редактора на части
    View
    (для наблюдателей) и
    Subject
    Родственные паттерны
    Посредник (319): класс
    ChangeManager действует как посредник между субъ- ектами и наблюдателями, инкапсулируя сложную семантику обновления.
    Одиночка (157): класс
    ChangeManager может воспользоваться паттерном одиночка, чтобы гарантировать уникальность и глобальную доступность менеджера изменений.
    ПАТТЕРН STATE (СОСТОЯНИЕ)
    Название и классификация паттерна
    Состояние — паттерн поведения объектов.
    Назначение
    Позволяет объекту изменять свое поведение в зависимости от внутреннего состояния. Извне создается впечатление, что изменился класс объекта.
    Мотивация
    Рассмотрим класс
    TCPConnection
    , представляющий сетевое соединение.
    Объект этого класса может находиться в одном из нескольких состояний:
    Established
    (установлено),
    Listening
    (прослушивание),
    Closed
    (закрыто).
    Когда объект
    TCPConnection получает запросы от других объектов, то в за- висимости от текущего состояния он отвечает по-разному. Например, ответ на запрос
    Open
    (открыть) зависит от того, находится ли соединение в состо- янии
    Closed или
    Established
    . Паттерн состояние описывает, каким образом объект
    TCPConnection может вести себя по-разному, находясь в различных состояниях.
    Основная идея этого паттерна заключается в том, чтобы ввести абстракт- ный класс
    TCPState для представления различных состояний соединения.
    Этот класс объявляет интерфейс, общий для всех классов, описывающих различные рабочие состояния. В подклассах
    TCPState реализовано по-

    Паттерн State (состояние)
    353
    ведение, специ фичное для конкретного состояния. Например, в классах
    TCPEstablished и
    TCPClosed реализовано поведение, характерное для со- стояний
    Established и
    Closed соответственно.
    TCPState
    Open()
    Close()
    Acknowledge()
    TCPListen
    Open()
    Close()
    Acknowledge()
    TCPClosed
    Open()
    Close()
    Acknowledge()
    TCPEstablished
    Open()
    Close()
    Acknowledge()
    TCPConnection
    Open()
    Close()
    Acknowledge()
    state–>Open()
    state
    Класс
    TCPConnection хранит у себя объект состояния (экземпляр некоторого подкласса
    TCPState
    ), представляющий текущее состояние соединения, и де- легирует все зависящие от состояния запросы этому объекту.
    TCPConnection использует свой экземпляр подкласса
    TCPState для выполнения операций, свойственных только данному состоянию соединения.
    При каждом изменении состояния соединения
    TCPConnection изменяет свой объект-состояние. Например, когда установленное соединение закрывает- ся,
    TCPConnection заменяет экземпляр класса
    TCPEstablished экземпляром
    TCPClosed
    Применимость
    Основные условия для применения паттерна состояние:
    „
    „
    поведение объекта зависит от его состояния и должно изменяться во время выполнения;
    „
    „
    когда в коде операций встречаются состоящие из многих ветвей услов- ные операторы, в которых выбор ветви зависит от состояния. Обычно в таком случае состояние представлено перечисляемыми константами.
    Часто одна и та же структура условного оператора повторяется в не- скольких операциях. Паттерн состояние предлагает поместить каждую ветвь в отдельный класс. Это позволяет трактовать состояние объекта как самостоятельный объект, который может изменяться независимо от других.

    354
    Глава 5. Паттерны поведения
    Структура
    Context
    Request()
    state–>Handle()
    state
    State
    Handle()
    ConcreteStateA
    Handle()
    ConcreteStateB
    Handle()
    Участники
    „
    „
    Context (
    TCPConnection
    ) — контекст:
    • определяет интерфейс, представляющий интерес для клиентов;
    • хранит экземпляр подкласса
    ConcreteState
    , которым определяется текущее состояние;
    „
    „
    State (
    TCPState
    ) — состояние:
    • определяет интерфейс для инкапсуляции поведения, ассоциирован- ного с конкретным состоянием контекста
    Context
    ;
    „
    „
    Подклассы
    ConcreteState
    (
    TCPEstablished
    ,
    TCPListen
    ,
    TCPClosed
    ) — конкретное состояние:
    • каждый подкласс реализует поведение, ассоциированное с некоторым состоянием контекста
    Context
    Отношения
    „
    „
    Класс
    Context делегирует зависящие от состояния запросы текущему объекту
    ConcreteState
    ;
    „
    „
    контекст может передать себя в качестве аргумента объекту
    State
    , кото- рый будет обрабатывать запрос. Это дает возможность объекту-состоя- нию при необходимости получить доступ к контексту;
    „
    „
    Context
    — это основной интерфейс для клиентов. Клиенты могут кон- фигурировать контекст объектами состояния
    State
    . Один раз сконфи- гурировав контекст, клиенты уже не должны напрямую связываться с объектами состояния;
    „
    „
    либо
    Context
    , либо подклассы
    ConcreteState могут решить, при каких условиях и в каком порядке происходит смена состояний.

    Паттерн State (состояние)
    355
    Результаты
    Результаты использования паттерна состояние:
    „
    „
    локализация поведения, зависящего от состояния, и деление его на части,
    соответствующие состояниям. Паттерн состояние помещает все пове- дение, ассоциированное с конкретным состоянием, в отдельный объект.
    Поскольку зависящий от состояния код целиком находится в одном из подклассов класса
    State
    , то добавлять новые состояния и переходы можно просто путем порождения новых подклассов. Вместо этого мож- но было бы использовать данные-члены для определения внутренних состояний, тогда операции объекта
    Context проверяли бы эти данные.
    Но в таком случае похожие условные операторы или операторы ветвле- ния были бы разбросаны по всему коду класса
    Context
    . При этом добав- ление нового состояния потребовало бы изменения нескольких опера- ций, что затруднило бы сопровождение.
    Паттерн состояние позволяет решить эту проблему, но одновременно порождает другую, поскольку поведение для различных состояний ока- зывается распределенным между несколькими подклассами
    State
    . Это увеличивает число классов. Конечно, один класс компактнее, но если со- стояний много, то такое распределение эффективнее, так как в противном случае пришлось бы иметь дело с громоздкими условными операторами.
    Наличие громоздких условных операторов нежелательно, равно как и длинных процедур. Они слишком монолитны, поэтому с модификацией и расширением кода возникают проблемы. Паттерн состояние предлагает более удачный способ структурирования зависящего от состояния кода.
    Логика, описывающая переходы между состояниями, больше не заклю- чена в монолитные операторы if или switch
    , а распределена между под- классами
    State
    . При инкапсуляции каждого перехода и действия в класс состояние становится полноценным объектом. Это улучшает структуру кода и проясняет его назначение;
    „
    „
    явно выраженные переходы между состояниями. Если объект определя- ет свое текущее состояние исключительно в терминах внутренних дан- ных, то переходы между состояниями не имеют явного представления; они проявляются лишь как присваивания некоторым переменным. Ввод отдельных объектов для различных состояний делает переходы более явными. Кроме того, объекты
    State могут защитить контекст
    Context от рассогласования внутренних переменных, поскольку переходы с точки зрения контекста — это атомарные действия. Для осуществления пере- хода надо изменить значение только одной переменной (объектной пе- ременной
    State в классе
    Context
    ), а не нескольких [dCLF93];

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


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