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

  • Caretaker SetMemento(Memento m)CreateMemento()stateOriginator GetState()SetState()stateХранительMemento

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


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

    325
    ниваться информацией более свободно. В Smalltalk/V для Windows применяется разновидность делегирования: при взаимодействии с по- средником коллега передает себя в аргументе, давая посреднику возмож- ность идентифицировать отправителя. Об этом подходе рассказывается в разделе «Пример кода», а о реализации в Smalltalk/V — в разделе
    «Известные применения».
    Пример кода
    Для создания диалогового окна, обсуждавшегося в разделе «Мотивация», воспользуемся классом
    DialogDirector
    . Абстрактный класс
    DialogDirector определяет интерфейс распорядителей:
    class DialogDirector {
    public:
    virtual

    DialogDirector();
    virtual void ShowDialog();
    virtual void WidgetChanged(Widget*) = 0;
    protected:
    DialogDirector();
    virtual void CreateWidgets() = 0;
    };
    Widget
    — абстрактный базовый класс для всех виджетов. Он располагает информацией о своем распорядителе:
    class Widget { public:
    Widget(DialogDirector*); virtual void Changed(); virtual void HandleMouse(MouseEvent& event);
    // ... private:
    DialogDirector* _director;
    };
    Changed вызывает операцию распорядителя
    WidgetChanged
    . С ее помощью виджеты информируют своего распорядителя о происшедших с ними из- менениях:
    void Widget::Changed () {
    _director->WidgetChanged(this);
    }

    326
    Глава 5. Паттерны поведения
    В подклассах
    DialogDirector переопределена операция
    WidgetChanged для воздействия на нужные виджеты. Виджет передает ссылку на самого себя в аргументе
    WidgetChanged
    , чтобы распорядитель имел информацию об из- менившемся виджете. Подклассы
    DialogDirector переопределяют исключи- тельно виртуальную функцию
    CreateWidgets для размещения в диалоговом окне нужных виджетов.
    ListBox
    ,
    EntryField и
    Button
    — это подклассы
    Widget для специализирован- ных элементов интерфейса. В классе
    ListBox есть операция
    GetSelection для получения текущего множества выделенных элементов, а в классе
    EntryField
    — операция
    SetText для размещения текста в поле ввода:
    class ListBox : public Widget {
    public:
    ListBox(DialogDirector*);
    virtual const char* GetSelection();
    virtual void SetList(List* listItems);
    virtual void HandleMouse(MouseEvent& event);
    // ...
    };
    class EntryField : public Widget {
    public:
    EntryField(DialogDirector*);
    virtual void SetText(const char* text);
    virtual const char* GetText();
    virtual void HandleMouse(MouseEvent& event);
    // ...
    };
    Операция
    Changed вызывается при нажатии кнопки
    Button
    (простой виджет).
    Это происходит в операции обработки событий мыши
    HandleMouse
    :
    class Button : public Widget {
    public:
    Button(DialogDirector*);
    virtual void SetText(const char* text);
    virtual void HandleMouse(MouseEvent& event);
    // ...
    };
    void Button::HandleMouse (MouseEvent& event) {
    // ...
    Changed();
    }

    Паттерн Mediator (посредник)
    327
    Класс
    FontDialogDirector является посредником между всеми виджетами в диалоговом окне.
    FontDialogDirector
    — это подкласс класса
    DialogDirector
    :
    class FontDialogDirector : public DialogDirector {
    public:
    FontDialogDirector();
    virtual FontDialogDirector();
    virtual void WidgetChanged(Widget*);
    protected:
    virtual void CreateWidgets();
    private:
    Button* _ok;
    Button* _cancel;
    ListBox* _fontList;
    EntryField* _fontName;
    };
    FontDialogDirector отслеживает все виджеты, которые ранее поместил в диалоговое окно. Переопределенная в нем операция
    CreateWidgets создает виджеты и инициализирует ссылки на них:
    void FontDialogDirector::CreateWidgets () {
    _ok = new Button(this);
    _cancel = new Button(this);
    _fontList = new ListBox(this);
    _fontName = new EntryField(this);
    // Поместить в список названия шрифтов
    // Разместить все виджеты в диалоговом окне
    }
    Операция
    WidgetChanged обеспечивает правильную совместную работу виджетов:
    void FontDialogDirector::WidgetChanged (
    Widget* theChangedWidget
    ) {
    if (theChangedWidget == _fontList) {
    _fontName->SetText(_fontList->GetSelection());
    } else if (theChangedWidget == _ok) {
    // Изменить шрифт и уничтожить диалоговое окно
    // ...
    } else if (theChangedWidget == _cancel) {
    // закрыть диалог
    }
    }

    328
    Глава 5. Паттерны поведения
    Сложность операции
    WidgetChanged возрастает пропорционально сложности диалогового окна. Конечно, создание очень больших диалоговых окон неже- лательно по другим причинам, но в других ситуациях сложность посредника может свести на нет его преимущества.
    Известные применения
    И в ET++ [WGM88], и в библиотеке классов THINK C [Sym93b] применя- ются похожие на нашего распорядителя объекты для реализации посредни- чества между виджетами в диалоговых окнах.
    Архитектура приложения в Smalltalk/V для Windows основана на структуре посредника [LaL94]. В этой среде приложение состоит из окна
    Window
    , кото- рое содержит набор панелей. В библиотеке есть несколько предопределен- ных объектов-панелей
    Pane
    , например:
    TextPane
    ,
    ListBox
    ,
    Button и т. д. Их можно использовать без подклассов. Разработчик приложения порождает подклассы только от класса
    ViewManager
    (диспетчер видов), отвечающего за обмен информацией между панелями.
    ViewManager
    — это посредник, каждая панель знает своего диспетчера, который считается «владельцем» панели.
    Панели не ссылаются друг на друга напрямую.
    На изображенной схеме объектов показан снимок работающего приложения на стадии выполнения.
    aTextPane
    Владелец
    aListBox
    Владелец
    aButton
    Владелец
    aViewManager
    textPane listBox button
    В Smalltalk/V для обмена информацией между объектами
    Pane и
    ViewManager используется механизм событий. Панель генерирует событие для получения данных от своего посредника или уведомления его о чем-то важном. С каж-

    Паттерн Mediator (посредник)
    329
    дым событием связан символ (например,
    #select
    ), который однозначно его идентифицирует. Диспетчер видов регистрирует вместе с панелью селектор метода, который является обработчиком события.
    Из следующего фрагмента кода видно, как объект
    ListPane создается внутри подкласса
    ViewManager и как
    ViewManager регистрирует обработчик события
    #select
    :
    self addSubpane: (ListPane new paneName: 'myListPane';
    owner: self;
    when: #select perform: #listSelect:).
    При координации сложных обновлений также требуется паттерн посредник.
    Примером может служить класс
    ChangeManager
    , упомянутый в описании паттерна наблюдатель (339). Этот класс осуществляет посредничество между субъектами и наблюдателями, чтобы не делать лишних обновлений.
    Когда объект изменяется, он извещает
    ChangeManager
    , который координирует обновление и информирует все необходимые объекты.
    Аналогичным образом посредник применяется в графических редак- торах Unidraw [VL90], где используется класс
    CSolver
    , следящий за соблюдением ограничений связанности между коннекторами. Объекты в графических редакторах могут быть визуально соединены между со- бой различными способами. Коннекторы полезны в приложениях, кото- рые автоматически поддерживают связанность, например в редакторах диаграмм и в системах проектирования электронных схем. Класс
    CSolver является посредником между коннекторами. Он преодолевает ограниче- ния связанности и обновляет позиции коннекторов, так чтобы отразить изменения.
    Родственные паттерны
    Фасад (221) отличается от посредника тем, что он абстрагирует некоторую подсистему объектов для предоставления более удобного интерфейса. Его протокол однонаправленный, то есть объекты фасада направляют запросы классам подсистемы, но не наоборот. Посредник же обеспечивает совместное поведение, которое объекты-коллеги не могут или не «хотят» реализовывать, и его протокол двунаправленный.
    Коллеги могут обмениваться информацией с посредником с помощью пат- терна наблюдатель (339).

    330
    Глава 5. Паттерны поведения
    ПАТТЕРН MEMENTO (ХРАНИТЕЛЬ)
    Название и классификация паттерна
    Хранитель — паттерн поведения объектов.
    Назначение
    Не нарушая инкапсуляции, фиксирует и выносит за пределы объекта его внутреннее состояние, так чтобы позднее можно было восстановить в нем объект.
    Другие названия
    Token
    (лексема).
    Мотивация
    Иногда требуется тем или иным способом зафиксировать внутреннее со- стояние объекта. Такая потребность возникает, например, при реализации контрольных точек и механизмов отмены, позволяющих пользователю от- менить пробную операцию или восстановить состояние после ошибки. Его необходимо где-то сохранить, чтобы позднее восстановить в нем объект. Но обычно объекты инкапсулируют все свое состояние полностью или частично, делая его недоступным для других объектов, так что внешнее сохранение состояния невозможно. Раскрытие же состояния нарушило бы принцип инкапсуляции и поставило бы под угрозу надежность и расширяемость приложения.
    Рассмотрим, например, графический редактор с возможностью связывания объектов. Пользователь может соединить два прямоугольника линией, и они останутся соединенными при любых перемещениях. Редактор сам перери- совывает линию, сохраняя связанность конфигурации.
    Система разрешения ограничений — хорошо известный способ поддержания связанности между объектами. Ее функции могут выполняться объектом

    Паттерн Memento (хранитель)
    331
    класса
    ConstraintSolver
    , который регистрирует вновь создаваемые соеди- нения и генерирует описывающие их математические уравнения. А когда пользователь каким-то образом модифицирует диаграмму, объект решает эти уравнения. На основании результатов вычислений объект
    ConstraintSolver перерисовывает графику так, чтобы были сохранены все соединения.
    Поддержка отмены операций в приложениях не так проста, как может по- казаться на первый взгляд. Очевидный способ отменить операцию пере- мещения — сохранение расстояния между старым и новым положением с последующим перемещением объекта на такое же расстояние назад. Однако такое решение не гарантирует, что все объекты окажутся в исходных местах.
    Допустим, в размещении соединительной линии есть некоторая неопреде- ленность; тогда простое перемещение прямоугольника на прежнее место может не привести к желаемому эффекту.
    В общем случае открытого интерфейса
    ConstraintSolver может быть недо- статочно для точной отмены всех изменений смежных объектов. Механизм отмены должен работать в тесном взаимодействии с
    ConstraintSolver для восстановления предыдущего состояния, но необходимо также позаботиться о том, чтобы внутренние подробности
    ConstraintSolver не были доступны этому механизму.
    Паттерн хранитель поможет решить данную проблему. Хранитель — это объ- ект, в котором сохраняется внутреннее состояние другого объекта — хозяина хранителя. Для работы механизма отмены нужно, чтобы хозяин предоставил хранитель, когда возникнет необходимость записать контрольную точку со- стояния хозяина. Только хозяину разрешено помещать в хранитель инфор- мацию и извлекать ее оттуда, для других объектов хранитель непрозрачен.
    В примере графического редактора, который обсуждался выше, в роли хозяина может выступать объект
    ConstraintSolver
    . Процесс отмены харак- теризуется следующей последовательностью событий:
    1. Редактор запрашивает хранитель у объекта
    ConstraintSolver в процессе выполнения операции перемещения.

    332
    Глава 5. Паттерны поведения
    2.
    ConstraintSolver создает и возвращает хранитель, в данном случае эк- земпляр класса
    SolverState
    . Хранитель
    SolverState содержит структуры данных, описывающие текущее состояние внутренних уравнений и пере- менных
    ConstraintSolver
    3. Позже, когда пользователь отменяет операцию перемещения, редактор возвращает
    SolverState объекту
    ConstraintSolver
    4. Основываясь на информации, которая хранится в объекте
    SolverState
    ,
    ConstraintSolver изменяет свои внутренние структуры, возвращая фор- мулы и переменные в первоначальное состояние.
    Такая организация позволяет объекту
    ConstraintSolver доверить другим объектам информацию, необходимую для возврата в предыдущее состояние, не раскрывая в то же время свою внутреннюю структуру и представление.
    Применимость
    Основные условия для применения паттерна хранитель:
    „
    „
    необходимость сохранения снимка состояния объекта (или его части), чтобы впоследствии объект можно было восстановить в том же состо- янии, и
    „
    „
    прямой интерфейс для получения этого состояния привел бы к раскры- тию подробностей реализации и нарушению инкапсуляции объекта.
    Структура
    Caretaker
    SetMemento(Memento m)
    CreateMemento()
    state
    Originator
    GetState()
    SetState()
    state
    Хранитель
    Memento
    return new Memento(state)
    state = m  GetState()
    >
    Участники
    „
    „
    Memento (
    SolverState
    ) — хранитель:
    • сохраняет внутреннее состояние объекта
    Originator
    . Объем сохра- няемой информации может быть различным и определяется потреб- ностями хозяина;

    Паттерн Memento (хранитель)
    333
    • запрещает доступ всем другим объектам, кроме хозяина. По существу, у хранителей есть два интерфейса. «Посыльный»
    Caretaker видит лишь «узкий» интерфейс хранителя — он может только передавать хранитель другим объектам. Напротив, хозяину доступен «широкий» интерфейс, который обеспечивает доступ ко всем данным, необхо- димым для восстановления в прежнем состоянии. Идеальный вари- ант — когда только хозяину, создавшему хранитель, открыт доступ к внутреннему состоянию последнего;
    „
    „
    Originator (
    ConstraintSolver
    ) — хозяин:
    • создает хранитель, содержащий снимок текущего внутреннего со- стояния;
    • использует хранитель для восстановления внутреннего состояния;
    „
    „
    Caretaker (механизм отката) — посыльный:
    • отвечает за сохранение хранителя;
    • никогда не выполняет операции с хранителем и не анализирует его внутреннее содержимое.
    Отношения
    „
    „
    посыльный запрашивает хранитель у хозяина, некоторое время держит его у себя, а затем возвращает хозяину, как показано на следующей диа- грамме взаимодействий.
    aCaretaker anOriginator aMemento
    CreateMemento()
    new Memento
    SetMemento(aMemento)
    SetState()
    GetState()
    Иногда этого не происходит, так как последнему не нужно восстанавли- вать прежнее состояние;
    „
    „
    хранители пассивны. Только хозяин, создавший хранитель, имеет до- ступ к информации о состоянии.

    334
    Глава 5. Паттерны поведения
    Результаты
    Основные достоинства и недостатки паттерна хранитель:
    „
    „
    сохранение границ инкапсуляции. Хранитель позволяет избежать рас- крытия информации, которой должен распоряжаться только хозяин, но которую тем не менее необходимо хранить вне последнего. Этот паттерн изолирует объекты от потенциально сложного внутреннего устройства хозяина, не изменяя границы инкапсуляции;
    „
    „
    упрощение структуры хозяина. При других вариантах дизайна, сохра- няющего границы инкапсуляции, хозяин хранит внутри себя версии внутреннего состояния, которое запрашивали клиенты. Таким образом, вся ответственность за управление памятью лежит на хозяине. При пере- кладывании заботы о запрошенном состоянии на клиентов упрощается структура хозяина, а клиентам дается возможность не информировать хозяина о том, что они закончили работу;
    „
    „
    потенциальные затраты при использовании хранителей. С хранителями могут быть связаны заметные затраты, если хозяин должен копировать большой объем информации для сохранения хранителя в памяти, или если клиенты создают и возвращают хранители достаточно часто. Если затраты на инкапсуляцию и восстановление состояния хозяина велики, то этот паттерн не всегда подходит (см. также обсуждение инкремент- ности в разделе «Реализация»);
    „
    „
    определение «узкого» и «широкого» интерфейсов. В некоторых языках сложно гарантировать, что только хозяин может получить доступ к со- стоянию хранителя;
    „
    „
    скрытая плата за содержание хранителя. Посыльный отвечает за уда- ление хранителя, однако не располагает информацией о том, какой объ- ем информации о состоянии скрыт в нем. Следовательно, нетребова- тельный к ресурсам посыльный может расходовать очень много памяти при работе с хранителем.
    Реализация
    При рассмотрении паттерна хранитель следует обратить внимание на сле- дующие аспекты:
    „
    „
    языковая поддержка. У хранителей есть два интерфейса: «широкий» для хозяев и «узкий» для всех остальных объектов. В идеале язык ре- ализации должен поддерживать два уровня статического контроля до- ступа. В C++ это возможно, если объявить хозяина другом хранителя и сделать закрытым «широкий» интерфейс последнего (с помощью

    Паттерн Memento (хранитель)
    335
    ключевого слова private
    ). Открытым (
    public
    ) остается только «узкий» интерфейс. Пример:
    class State;
    class Originator {
    public:
    Memento* CreateMemento();
    void SetMemento(const Memento*);
    // ...
    private:
    State* _state;
    // Внутренние структуры данных
    // ...
    };
    class Memento {
    public:
    // Узкий открытый интерфейс virtual Memento();
    private:
    // Закрытые члены, доступные только хозяину Originator friend class Originator;
    Memento();
    void SetState(State*);
    State* GetState();
    // ...
    private:
    State* _state;
    // ...
    };
    „
    „
    сохранение инкрементных изменений. Если хранители создаются и воз- вращаются своему хозяину в предсказуемой последовательности, то хранитель может сохранить лишь инкрементные изменения во внутрен- нем состоянии хозяина.
    Например, допускающие отмену команды в списке истории могут пользо- ваться хранителями для восстановления первоначального состояния (см. описание паттерна команда (275)). Список истории предназначен только для отмены и повтора команд. Это означает, что хранители могут работать лишь с изменениями, сделанными командой, а не с полным состоянием объекта. В примере из раздела «Мотивация» объект, отменяющий огра- ничения, может хранить только такие внутренние структуры, которые изменяются с целью сохранить линию, соединяющую прямоугольники, а не абсолютные позиции всех объектов.

    1   ...   24   25   26   27   28   29   30   31   ...   38


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