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

  • Посредник Коллеги

  • Mediator

  • ConcreteMediator

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


    Скачать 6.37 Mb.
    НазваниеЭ. Гамма, Р. Хелм
    АнкорFactorial
    Дата14.03.2022
    Размер6.37 Mb.
    Формат файлаpdf
    Имя файлаPatterny_Obektno-Orientirovannogo_Proektirovania_2020.pdf
    ТипДокументы
    #395452
    страница27 из 38
    1   ...   23   24   25   26   27   28   29   30   ...   38
    314
    Глава 5. Паттерны поведения
    Iterator& operator*() { return *_i; }
    private:
    // Запретить копирование и присваивание, чтобы
    // избежать многократных удалений _i:
    IteratorPtr(const IteratorPtr&);
    IteratorPtr& operator=(const IteratorPtr&);
    private:
    Iterator* _i;
    };
    IteratorPtr позволяет упростить код печати:
    AbstractList* employees;
    // ...
    IteratorPtr iterator(employees->CreateIterator());
    PrintEmployees(*iterator);
    „
    „
    внутренний ListIterator. В последнем примере рассмотрим, как можно было бы реализовать внутренний (или пассивный) класс
    ListIterator
    В этом случае итератор сам управляет итерацией и применяет к каждо- му элементу некоторую операцию.
    Проблема в том, как параметризовать итератор той операцией, которую мы хотим применить к каждому элементу. C++ не поддерживает ни анонимных функций, ни замыканий, которые предусмотрены для этой цели в других языках. Есть по крайней мере два варианта: (1) передать указатель на функцию (глобальную или статическую) и (2) породить под- классы. В первом случае итератор вызывает переданную ему операцию в каждой точке обхода. Во втором случае итератор вызывает операцию, которая замещена в подклассе и обеспечивает нужное поведение.
    Ни один из вариантов не идеален. Часто во время обхода нужно накапли- вать некоторую информацию, а функции для этого плохо подходят — для запоминания состояния пришлось бы использовать статические перемен- ные. Подкласс класса
    Iterator предоставляет удобное место для хранения накопленного состояния — переменную экземпляра. Однако создавать подкласс для каждого вида обхода слишком трудоемко.
    Ниже приведен черновик реализации второго варианта с использованием подклассов. Назовем внутренний итератор
    ListTraverser
    :
    template
    class ListTraverser {
    public:
    ListTraverser(List* aList);

    Паттерн Iterator (итератор)
    315
    bool Traverse();
    protected:
    virtual bool ProcessItem(const Item&) = 0;
    private:
    ListIterator _iterator;
    };
    ListTraverser получает экземпляр
    List в параметре. Во внутренней реализации он использует внешний итератор
    ListIterator для выполне- ния обхода. Операция
    Traverse начинает обход и вызывает для каждого элемента операцию
    ProcessItem
    . Внутренний итератор может закончить обход, вернув false из
    ProcessItem
    Traverse сообщает о преждевремен- ном завершении обхода:
    template
    ListTraverser::ListTraverser (
    List* aList
    ) : _iterator(aList) { }
    template
    bool ListTraverser::Traverse () { bool result = false; for (
    _iterator.First();
    !_iterator.IsDone();
    _iterator.Next()
    ) {
    result = ProcessItem(_iterator.CurrentItem()); if (result == false) {
    break;
    }
    } return result;
    }
    Воспользуемся итератором
    ListTraverser для вывода первых десяти элементов списка. С этой целью надо породить подкласс от
    ListTraverser и определить в нем операцию
    ProcessItem
    . Для подсчета выведенных элементов используется переменная экземпляра
    _count
    :
    class PrintNEmployees : public ListTraverser {
    public:
    PrintNEmployees(List* aList, int n) :
    ListTraverser(aList),
    _total(n), _count(0) { }

    316
    Глава 5. Паттерны поведения protected:
    bool ProcessItem(Employee* const&);
    private:
    int _total;
    int _count;
    };
    bool PrintNEmployees::ProcessItem (Employee* const& e) {
    _count++;
    e->Print();
    return _count < _total;
    }
    Вот как
    PrintNEmployees выводит первые 10 элементов:
    List* employees;
    // ...
    PrintNEmployees pa(employees, 10);
    pa.Traverse();
    Обратите внимание, что в коде клиента нет цикла итерации. Всю логику обхода можно использовать повторно. В этом и состоит основное преиму- щество внутреннего итератора. Правда, работы немного больше, чем для внешнего итератора, так как нужно определять новый класс. Сравните с программой, где применяется внешний итератор:
    ListIterator i(employees);
    int count = 0;
    for (i.First(); !i.IsDone(); i.Next()) {
    count++;
    i.CurrentItem()->Print(); if (count >= 10) { break;
    }
    }
    Внутренние итераторы могут инкапсулировать разные виды итераций.
    Например,
    FilteringListTraverser инкапсулирует итерацию, при кото- рой обрабатываются лишь элементы, удовлетворяющие определенному условию:
    template
    class FilteringListTraverser {
    public:
    FilteringListTraverser(List* aList);

    Паттерн Iterator (итератор)
    317
    bool Traverse();
    protected:
    virtual bool ProcessItem(const Item&) = 0;
    virtual bool TestItem(const Item&) = 0;
    private:
    ListIterator _iterator;
    };
    Интерфейс такой же, как у
    ListTraverser
    , если не считать новой функции, которая и реализует проверку условия. В подклассах
    TestItem замещается для задания конкретного условия.
    Операция
    Traverse выясняет, нужно ли продолжать обход по результатам проверки:
    template
    void FilteringListTraverser::Traverse () {
    bool result = false;
    for (
    _iterator.First();
    !_iterator.IsDone();
    _iterator.Next()
    ) {
    if (TestItem(_iterator.CurrentItem())) {
    result = ProcessItem(_iterator.CurrentItem());
    if (result == false) {
    break;
    }
    }
    }
    return result;
    }
    В качестве варианта можно было определить функцию
    Traverse так, чтобы она сообщала хотя бы об одном встретившемся элементе, который удовлетворяет условию
    1
    Известные применения
    Итераторы широко распространены в объектно-ориентированных систе- мах. В том или ином виде они поддерживаются в большинстве библиотек коллекций классов.
    1
    Операция
    Traverse в этих примерах — это ничто иное, как шаблонный метод с при- митивными операциями
    TestItem и
    ProcessItem

    318
    Глава 5. Паттерны поведения
    Вот пример из библиотеки компонентов Грейди Буча [Boo94], популярной библиотеки, поддерживающей классы коллекций. В ней имеется реали- зация очереди фиксированной (ограниченной) и динамически растущей длины (неограниченной). Интерфейс очереди определен в абстрактном классе
    Queue
    . Для поддержки полиморфной итерации по очередям с разной реализацией итератор написан с использованием интерфейса абстракт- ного класса
    Queue
    . Преимущество такого подхода очевидно — отпадает необходимость в фабричном методе, который запрашивал бы у очереди соответствующий ей итератор. Но чтобы итератор можно было реали- зовать эффективно, интерфейс абстрактного класса
    Queue должен быть достаточно мощным.
    В языке Smalltalk необязательно определять итераторы так явно. В стан- дартных классах коллекций (
    Bag
    ,
    Set
    ,
    Dictionary
    ,
    OrderedCollection
    ,
    String и т. д.) определен метод do:
    , выполняющий функции внутреннего итератора, который получает блок (то есть замыкание) в аргументе. Каждый элемент коллекции привязывается к локальной переменной в блоке, после чего блок выполняется. Smalltalk также включает набор классов
    Stream
    , которые под- держивают интерфейс, сходный с интерфейсом итераторов.
    ReadStream
    — это фактически класс
    Iterator и внешний итератор для всех последовательных коллекций. У непоследовательных коллекций (таких как
    Set и
    Dictionary
    ) нет стандартных итераторов.
    Полиморфные итераторы и выполняющие очистку заместители находятся в контейнерных классах ET++ [WGM88]. Курсороподобные итераторы используются в классах каркаса графических редакторов Unidraw [VL90].
    В системе ObjectWindows 2.0 [Bor94] имеется иерархия классов итераторов для контейнеров. Контейнеры разных типов можно обходить одним и тем же способом. Синтаксис итераторов в ObjectWindows основан на пере- грузке постфиксного оператора инкремента
    ++
    для перехода к следующему элементу.
    Родственные паттерны
    Компоновщик (196): итераторы довольно часто применяются для обхода рекурсивных структур, создаваемых компоновщиком.
    Фабричный метод (135): полиморфные итераторы поручают фабричным методам создавать экземпляры подходящих подклассов класса
    Iterator
    Итератор (302) может использовать хранитель для сохранения состояния итерации и при этом содержит его внутри себя.

    Паттерн Mediator (посредник)
    319
    ПАТТЕРН MEDIATOR (ПОСРЕДНИК)
    Название и классификация паттерна
    Посредник — паттерн поведения объектов.
    Назначение
    Определяет объект, инкапсулирующий способ взаимодействия множества объектов. Посредник обеспечивает слабую связанность системы, избавляя объекты от необходимости явно ссылаться друг на друга и позволяя тем самым независимо изменять взаимодействия между ними.
    Мотивация
    Объектно-ориентированное проектирование способствует распределению некоторого поведения между объектами. Но при этом в получившейся структуре объектов может возникнуть много связей или (в худшем случае) каждый объект должен располагать информацией обо всех остальных.
    Хотя разбиение системы на множество объектов в общем случае повышает степень повторного использования, размножение взаимосвязей приводит к обратному эффекту. Если взаимосвязей слишком много, тогда система подобна монолиту и маловероятно, что объект сможет работать без под- держки других объектов. Более того, существенно изменить поведение си- стемы практически невозможно, поскольку оно распределено между мно- гими объектами. В результате для настройки поведения системы вам придется определять множество под- классов.
    Для примера рассмотрим реализа- цию диалоговых окон в графическом интерфейсе пользователя. Здесь рас- полагается ряд виджетов: кнопки, меню, поля ввода и т. д., как показано на рисунке.
    Часто между виджетами в диалого- вом окне существуют зависимости.
    Например, если одно из полей ввода пустое, то определенная кнопка мо- жет быть недоступной, а при выборе

    320
    Глава 5. Паттерны поведения из списка может измениться содержимое поля ввода. И наоборот, ввод текста в некоторое поле может автоматически привести к выбору одного или нескольких элементов списка. Если в поле ввода присутствует какой- то текст, то могут быть активизированы кнопки, позволяющие произвести определенное действие над этим текстом (например, изменить либо удалить объект, на который он ссылается).
    В разных диалоговых окнах зависимости между виджетами могут быть различными. Поэтому, несмотря на то что во всех окнах встречаются одно- типные виджеты, просто взять и повторно использовать готовые классы виджетов не удастся, придется производить настройку с целью учета за- висимостей. Индивидуальная настройка каждого виджета — утомительное занятие, ибо участвующих классов слишком много.
    Всех этих проблем можно избежать, если инкапсулировать коллективное поведение в отдельном объекте-посреднике. Посредник отвечает за коорди- нацию взаимодействий между группой объектов. Он избавляет входящие в группу объекты от необходимости явно ссылаться друг на друга. Все объ- екты располагают информацией только о посреднике, поэтому количество взаимосвязей сокращается.
    Так, класс
    FontDialogDirector может служить посредником между виджета- ми в диалоговом окне. Объект этого класса «знает» обо всех виджетах в окне и координирует взаимодействие между ними, то есть выполняет функции центра коммуникаций.
    aListBox
    Распорядитель
    aFontDialogDirector
    aClient
    Распорядитель
    aButton
    Распорядитель
    anEntryField
    Распорядитель

    Паттерн Mediator (посредник)
    321
    На следующей схеме взаимодействий показано, как объекты кооперируются друг с другом, реагируя на изменение выбранного элемента списка.
    aClient aFontDialogDirector
    ShowDialog()
    aListBox
    Посредник
    Коллеги
    WidgetChanged()
    GetSelection()
    SeeText()
    anEntryField
    Последовательность событий, в результате которых информация о выбран- ном элементе списка передается в поле ввода, выглядит так:
    1. Список сообщает распорядителю о происшедших в нем изменениях.
    2. Распорядитель получает от списка выбранный элемент.
    3. Распорядитель передает выбранный элемент полю ввода.
    4. Теперь, когда поле ввода содержит какую-то информацию, распорядитель активизирует кнопки для выполнения определенного действия (напри- мер, замены обычного шрифта на полужирный или курсив).
    Обратите внимание на то, как распорядитель осуществляет посредниче- ство между списком и полем ввода. Виджеты общаются друг с другом не напрямую, а только косвенно через распорядителя. Им вообще не нужно владеть информацией друг о друге, они осведомлены лишь о существовании распорядителя. А поскольку поведение локализовано в одном классе, то его несложно модифицировать или полностью заменить посредством расшире- ния или замены этого класса.
    Абстракцию
    FontDialogDirector можно было бы интегрировать в библио- теку классов так, как показано на схеме.

    322
    Глава 5. Паттерны поведения
    CreateWidgets()
    WidgetChanged(Widget)
    FontDialogDirector
    Changed()
    Widget
    SetText()
    EntryField
    GetSelection()
    ListBox
    ShowDialog()
    CreateWidgets()
    WidgetChanged(Widget)
    DialogDirector
    director  WidgetChanged(this)
    >
    director
    Список
    Поле ввода
    DialogDirector
    — это абстрактный класс, который определяет поведение диалогового окна в целом. Клиенты вызывают его операцию
    ShowDialog для отображения окна на экране.
    CreateWidgets
    — это абстрактная опера- ция для создания виджетов в диалоговом окне.
    WidgetChanged
    — еще одна абстрактная операция; с ее помощью виджеты сообщают распорядителю об изменениях. Подклассы
    DialogDirector замещают операции
    CreateWidgets
    (для создания нужных виджетов) и
    WidgetChanged
    (для обработки извещений об изменениях).
    Применимость
    Основные условия для применения паттерна посредник:
    „
    „
    существование объектов, связи между которыми сложны и четко опре- делены. Получающиеся при этом взаимозависимости не структуриро- ваны и трудны для понимания;
    „
    „
    повторное использование объекта затруднено, поскольку он обменива- ется информацией со многими другими объектами;
    „
    „
    поведение, распределенное между несколькими классами, должно на- страиваться без порождения множества подклассов.
    Структура
    Mediator
    ConcreteColleague1
    Посредник
    ConcreteMediator
    ConcreteColleague2
    Colleague

    Паттерн Mediator (посредник)
    323
    Типичная структура объектов может выглядеть так:
    aColleague
    Посредник
    aConcreteMediator
    aColleague
    Посредник
    aColleague
    Посредник
    aColleague
    Посредник
    aColleague
    Посредник
    Участники
    „
    „
    Mediator (
    DialogDirector
    ) — посредник:
    • определяет интерфейс для обмена информацией с объектами
    Colleague
    ;
    „
    „
    ConcreteMediator (
    FontDialogDirector
    ) — конкретный посредник:
    • реализует кооперативное поведение, координируя действия объектов
    Colleague
    ;
    • владеет информацией о коллегах и подсчитывает их;
    „
    „
    Классы
    Colleague (
    ListBox
    ,
    EntryField
    ) — коллеги:
    • каждый класс
    Colleague знает свой объект
    Mediator
    ;
    • все коллеги обмениваются информацией только с посредником во всех случаях, когда ему пришлось бы напрямую взаимодействовать с другими объектами.
    Отношения
    Коллеги посылают запросы посреднику и получают запросы от него. По- средник реализует кооперативное поведение путем переадресации каждого запроса подходящему коллеге (или нескольким коллегам).
    Результаты
    Основные достоинства и недостатки паттерна посредник:
    „
    „
    снижение числа порождаемых подклассов. Посредник локализует поведе- ние, которое в противном случае пришлось бы распределять между не-

    324
    Глава 5. Паттерны поведения сколькими объектами. Для изменения поведения нужно породить под- классы только от класса посредника
    Mediator
    , классы коллег
    Colleague можно использовать повторно без каких бы то ни было изменений;
    „
    „
    ослабление связей между коллегами. Посредник обеспечивает слабую связанность коллег. Изменять классы
    Colleague и
    Mediator можно не- зависимо друг от друга;
    „
    „
    упрощение протоколов взаимодействия объектов. Посредник заменяет вза- имодействия «все со всеми» взаимодействиями «один со всеми», то есть один посредник взаимодействует со всеми коллегами. Отношения вида
    «один ко многим» проще для понимания, сопровождения и расширения;
    „
    „
    абстрагирование способа кооперирования объектов. Выделение меха- низма посредничества в отдельную концепцию и инкапсуляция ее в од- ном объекте позволяет сосредоточиться именно на взаимодействии объектов, а не на их индивидуальном поведении. Это способствует про- яснению имеющихся в системе взаимодействий;
    „
    „
    централизация управления. Паттерн посредник заменяет сложность вза- имодействия сложностью класса-посредника. Поскольку посредник ин- капсулирует протоколы, то он может быть сложнее отдельных коллег.
    В результате сам посредник превращается монолит, который трудно со- провождать.
    Реализация
    При реализации посредника следует обратить внимание на следующие аспекты:
    „
    „
    избавление от абстрактного класса Mediator. Если коллеги работа- ют только с одним посредником, то нет необходимости определять абстрактный класс
    Mediator
    . Обеспечиваемая классом
    Mediator аб- стракция позволяет коллегам работать с разными подклассами класса
    Mediator и наоборот;
    „
    „
    обмен информацией между коллегами и посредником. Коллеги должны обмениваться информацией со своим посредником только тогда, когда возникает представляющее интерес событие. Одним из подходов к реа- лизации посредника является применение паттерна наблюдатель (339).
    Тогда классы коллег действуют как субъекты, посылающие извещения посреднику о любом изменении своего состояния. Посредник реагирует на них, сообщая об этом другим коллегам.
    При другом подходе в классе
    Mediator определяется специализиро- ванный интерфейс уведомления, который позволяет коллегам обме-

    Паттерн Mediator (посредник)
    1   ...   23   24   25   26   27   28   29   30   ...   38


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