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

  • ConcreteImplementor

  • Graphic

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


    Скачать 6.37 Mb.
    НазваниеЭ. Гамма, Р. Хелм
    АнкорFactorial
    Дата14.03.2022
    Размер6.37 Mb.
    Формат файлаpdf
    Имя файлаPatterny_Obektno-Orientirovannogo_Proektirovania_2020.pdf
    ТипДокументы
    #395452
    страница16 из 38
    1   ...   12   13   14   15   16   17   18   19   ...   38
    Implementor (
    WindowImp
    ) — реализатор:
    • определяет интерфейс для классов реализации. Он не обязан точно соответствовать интерфейсу класса
    Abstraction
    . На самом деле оба интерфейса могут быть совершенно различны. Обычно интерфейс класса
    Implementor предоставляет только примитивные операции, а класс
    Abstraction определяет операции более высокого уровня, основанные на этих примитивах;
    „
    „
    ConcreteImplementor (
    XWindowImp
    ,
    PMWindowImp
    ) — конкретный реализатор:
    • реализует интерфейс класса
    Implementor и определяет его конкретную реализацию.
    Отношения
    Объект
    Abstraction перенаправляет запросы клиента своему объекту
    Implementor
    Результаты
    Результаты применения паттерна мост:
    „
    „
    отделение реализации от интерфейса. Реализация больше не имеет по- стоянной привязки к интерфейсу. Реализация абстракции может на- страиваться во время выполнения. Объект может даже динамически изменять свою реализацию.
    Разделение классов
    Abstraction и
    Implementor устраняет также зависимо- сти от реализации, устанавливаемые на этапе компиляции. Чтобы изменить класс реализации, не обязательно перекомпилировать класс
    Abstraction и его клиентов. Это свойство особенно важно, если необходимо обеспечить двоичную совместимость между разными версиями библиотеки классов.
    Кроме того, такое разделение облегчает разбиение системы на слои и тем самым позволяет улучшить ее структуру. Высокоуровневые части систе- мы должны знать только о классах
    Abstraction и
    Implementor
    ;

    Паттерн Bridge (мост)
    189
    „
    „
    повышение степени расширяемости. Иерархии классов
    Abstraction и
    Implementor могут расширяться независимо;
    „
    „
    сокрытие деталей реализации от клиентов. Клиентов можно изолиро- вать от таких подробностей реализации, как совместное использование объектов класса
    Implementor и сопутствующего механизма подсчета ссылок.
    Реализация
    Если вы намереваетесь применить паттерн мост, то подумайте о таких аспек- тах реализации:
    „
    „
    только один класс Implementor. В ситуациях, когда есть только одна реа- лизация, создавать абстрактный класс
    Implementor необязательно. Это вырожденный случай паттерна мост — между классами
    Abstraction и
    Implementor существует взаимно однозначное соответствие. Тем не менее разделение все же полезно, если изменение реализации класса не должно отражаться на существующих клиентах (должно быть доста- точно заново скомпоновать программу, не перекомпилируя клиентский код).
    Для описания такого разделения Каролан (Carolan) [Car89] употребля- ет сочетание «чеширский кот». В C++ интерфейс класса
    Implementor можно определить в закрытом заголовочном файле, который не пере- дается клиентам. Это позволяет полностью скрыть реализацию класса от клиентов;
    „
    „
    создание правильного объекта Implementor. Как, когда и где принимается решение о том, экземпляр какого из классов
    Implementor следует соз- дать, если таких классов несколько?
    Если класс
    Abstraction располагает полной информацией обо всех классах
    ConcreteImplementor
    , то он может создать один из них в своем конструкторе; какой именно — зависит от переданных конструктору пара- метров. Так, если класс коллекции поддерживает несколько реализаций, то решение может приниматься в зависимости от размера коллекции.
    Для небольших коллекций применяется реализация в виде связанного списка, для больших — в виде хеш-таблиц.
    Другой подход — заранее выбрать реализацию по умолчанию, а позже изменять ее в соответствии с тем, как она используется. Например, если число элементов в коллекции превышает некоторую условную величину, то мы переключаемся с одной реализации на другую, более эффективную.

    190
    Глава 4. Структурные паттерны
    Также можно делегировать решение другому объекту. В примере с ие- рархиями
    Window
    /
    WindowImp уместно было бы ввести фабричный объект
    (см. паттерн абстрактная фабрика (113)), единственная задача которо- го — инкапсулировать платформенную специфику. Фабрика обладает информацией, объекты
    WindowImp какого вида надо создавать для данной платформы, а объект
    Window просто обращается к ней с запросом о предо- ставлении какого-нибудь объекта
    WindowImp и получает то, что нужно.
    Преимущество описанного подхода: класс
    Abstraction напрямую не привязан ни к одному из классов
    Implementor
    ;
    „
    „
    совместное использование реализаторов. Джеймс Коплиен показал, как в C++ можно применить идиому «описатель/тело», чтобы нескольки- ми объектами могла совместно использоваться одна и та же реализа- ция [Cop92]. В теле хранится счетчик ссылок, который увеличивается и уменьшается в классе описателя. Код для присваивания значений описателям, совместно использующим одно тело, в общем виде выгля- дит так:
    Handle& Handle::operator= (const Handle& other) { other._body->Ref();
    _body->Unref(); if (_body->RefCount() == 0) { delete _body;
    }
    _body = other._body; return *this;
    }
    „
    „
    применение множественного наследования. В C++ для объединения ин- терфейса с его реализацией можно воспользоваться множественным наследованием [Mar91]. Например, класс может открыто наследовать классу
    Abstraction и закрыто — классу
    ConcreteImplementor
    . Но такое решение зависит от статического наследования и жестко привязыва- ет реализацию к ее интерфейсу. Поэтому реализовать настоящий мост с помощью множественного наследования невозможно — по крайней мере в C++.
    Пример кода
    В следующем коде на C++ реализован пример
    Window
    /
    WindowImp
    , который обсуждался в разделе «Мотивация». Класс
    Window определяет абстракцию окна для клиентских приложений:

    Паттерн Bridge (мост)
    191
    class Window {
    public:
    Window(View* contents);
    // запросы, обрабатываемые окном virtual void DrawContents();
    virtual void Open();
    virtual void Close();
    virtual void Iconify();
    virtual void Deiconify();
    // запросы, перенаправляемые реализации virtual void SetOrigin(const Point& at);
    virtual void SetExtent(const Point& extent);
    virtual void Raise();
    virtual void Lower();
    virtual void DrawLine(const Point&, const Point&);
    virtual void DrawRect(const Point&, const Point&);
    virtual void DrawPolygon(const Point[], int n);
    virtual void DrawText(const char*, const Point&);
    protected:
    WindowImp* GetWindowImp();
    View* GetView();
    private:
    WindowImp* _imp;
    View* _contents; // содержимое окна
    };
    В классе
    Window хранится ссылка на
    WindowImp
    — абстрактный класс, в ко- тором объявлен интерфейс к данной оконной системе:
    class WindowImp {
    public:
    virtual void ImpTop() = 0;
    virtual void ImpBottom() = 0;
    virtual void ImpSetExtent(const Point&) = 0;
    virtual void ImpSetOrigin(const Point&) = 0;
    virtual void DeviceRect(Coord, Coord, Coord, Coord) = 0;
    virtual void DeviceText(const char*, Coord, Coord) = 0;
    virtual void DeviceBitmap(const char*, Coord, Coord) = 0;
    // множество других функций для рисования в окне...
    protected:
    WindowImp();
    };

    192
    Глава 4. Структурные паттерны
    Подклассы
    Window определяют различные виды окон, как то: окно при- ложения, пиктограмма, временное диалоговое окно, плавающая палитра инструментов и т. д.
    Например, класс
    ApplicationWindow реализует операцию
    DrawContents для отрисовки содержимого экземпляра класса
    View
    , который в нем хра- нится:
    class ApplicationWindow : public Window {
    public:
    // ...
    virtual void DrawContents();
    };
    void ApplicationWindow::DrawContents () {
    GetView()->DrawOn(this);
    }
    А в классе
    IconWindow хранится имя растрового изображения для пикто- граммы:
    class IconWindow : public Window { public:
    // ... virtual void DrawContents(); private: const char* _bitmapName;
    }; и реализация операции
    DrawContents для рисования этого изображения в окне:
    void IconWindow::DrawContents() {
    WindowImp* imp = GetWindowImp();
    if (imp != 0) {
    imp->DeviceBitmap(_bitmapName, 0.0, 0.0);
    }
    }
    Существует много других разновидностей класса
    Window
    . Окну класса
    TransientWindow иногда необходимо как-то сообщаться с создавшим его окном во время диалога, поэтому в объекте класса хранится ссылка на создателя. Окно класса
    PaletteWindow всегда располагается поверх других.
    Окно класса
    IconDockWindow
    (контейнер пиктограмм) хранит окна класса
    IconWindow и размещает их в ряд.

    Паттерн Bridge (мост)
    193
    Операции класса
    Window определяются в категориях интерфейса
    WindowImp
    Например,
    DrawRect вычисляет координаты по двум своим параметрам
    Point
    , перед тем как вызвать операцию
    WindowImp
    , которая рисует в окне прямоугольник:
    void Window::DrawRect (const Point& p1, const Point& p2) {
    WindowImp* imp = GetWindowImp();
    imp->DeviceRect(p1.X(), p1.Y(), p2.X(), p2.Y());
    }
    Конкретные подклассы
    WindowImp поддерживают разные оконные системы.
    Так, класс
    XWindowImp ориентирован на систему X Window:
    class XWindowImp : public WindowImp { public:
    XWindowImp(); virtual void DeviceRect(Coord, Coord, Coord, Coord);
    // прочие операции открытого интерфейса... private:
    // переменные, описывающие состояние, специфическое
    // для X Window system:
    Display* _dpy;
    Drawable _winid; // идентификатор окна
    GC _gc; // графический контекст окна
    };
    Для Presentation Manager (PM) определяется класс
    PMWindowImp
    :
    class PMWindowImp : public WindowImp { public:
    PMWindowImp(); virtual void DeviceRect(Coord, Coord, Coord, Coord);
    // прочие операции открытого интерфейса... private:
    // переменные, описывающие состояние, специфическое для PM:
    HPS _hps;
    };
    Эти подклассы реализуют операции
    WindowImp в контексте примитивов оконной системы. Например,
    DeviceRect для X Window реализуется так:
    void XWindowImp::DeviceRect (
    Coord x0, Coord y0, Coord x1, Coord y1
    ) {

    194
    Глава 4. Структурные паттерны int x = round(min(x0, x1));
    int y = round(min(y0, y1));
    int w = round(abs(x0 - x1));
    int h = round(abs(y0 - y1));
    XDrawRectangle(_dpy, _winid, _gc, x, y, w, h);
    }
    А реализация для PM выглядит так:
    void PMWindowImp::DeviceRect (
    Coord x0, Coord y0, Coord x1, Coord y1
    ) {
    Coord left = min(x0, x1);
    Coord right = max(x0, x1);
    Coord bottom = min(y0, y1);
    Coord top = max(y0, y1);
    PPOINTL point[4];
    point[0].x = left; point[0].y = top;
    point[1].x = right; point[1].y = top;
    point[2].x = right; point[2].y = bottom;
    point[3].x = left; point[3].y = bottom;
    if (
    (GpiBeginPath(_hps, 1L) == false) ||
    (GpiSetCurrentPosition(_hps, &point[3]) == false) ||
    (GpiPolyLine(_hps, 4L, point) == GPI_ERROR) ||
    (GpiEndPath(_hps) == false)
    ) {
    // сообщить об ошибке
    } else {
    GpiStrokePath(_hps, 1L, 0L);
    }
    }
    Как окно получает экземпляр нужного подкласса
    WindowImp
    ? В данном примере предполагается, что за это отвечает класс
    Window
    . Его операция
    GetWindowImp получает подходящий экземпляр от абстрактной фабрики (см. описание паттерна абстрактная фабрика (113)), которая инкапсулирует все зависимости от оконной системы.
    WindowImp* Window::GetWindowImp () { if (_imp == 0) {
    _imp = WindowSystemFactory::Instance()->MakeWindowImp();
    } return _imp;
    }

    Паттерн Bridge (мост)
    195
    WindowSystemFactory::Instance()
    возвращает абстрактную фабрику, кото- рая изготавливает все системнозависимые объекты. Для простоты мы сде- лали эту фабрику одиночкой (157) и позволили классу
    Window обращаться к ней напрямую.
    Известные применения
    Пример класса
    Window позаимствован из ET++ [WGM88]. В ET++ класс
    WindowImp называется
    WindowPort и имеет такие подклассы, как
    XWindowPort и
    SunWindowPort
    . Объект
    Window создает соответствующего себе реализатора
    Implementor
    , запрашивая его у абстрактной фабрики, которая называется
    WindowSystem
    . Эта фабрика предоставляет интерфейс для создания плат- форменнозависимых объектов: шрифтов, курсоров, растровых изображений и т. д.
    Дизайн классов
    Window
    /
    WindowPort в ET++ обобщает паттерн мост в том отношении, что
    WindowPort сохраняет также обратную ссылку на
    Window
    Класс-реализатор
    WindowPort использует эту ссылку для уведомления
    Window о событиях, специфичных для
    WindowPort
    : поступлении событий ввода, из- менениях размера окна и т. д.
    В работах Джеймса Коплиена [Cop92] и Бьерна Страуструпа [Str91] упо- минаются классы описателей и приводятся некоторые примеры. Основной акцент в этих примерах сделан на аспектах управления памятью, например совместном использовании представления строк и поддержки объектов переменного размера. Нас же в первую очередь интересует поддержка не- зависимых расширений абстракции и ее реализации.
    В библиотеке libg++ [Lea88] определены классы, которые реализуют рас- пространенные структуры данных:
    Set
    (множество),
    LinkedSet
    (множество как связанный список),
    HashSet
    (множество как хеш-таблица),
    LinkedList
    (связанный список) и
    HashTable
    (хеш-таблица).
    Set
    — это абстрактный класс, определяющий абстракцию множества, а
    LinkedList и
    HashTable
    — конкрет- ные реализации связанного списка и хеш-таблицы.
    LinkedSet и
    HashSet
    — реализаторы абстракции
    Set
    , формирующие мост между
    Set и
    LinkedList и
    HashTable соответственно. Перед вами пример вырожденного моста, по- скольку абстрактного класса
    Implementor здесь нет.
    В библиотеке NeXT AppKit [Add94] паттерн мост используется при реа- лизации и отображении графических изображений. Рисунок может быть представлен по-разному. Оптимальный способ его отображения на экране зависит от свойств дисплея и прежде всего от числа цветов и разрешения.
    Если бы не AppKit, то для каждого приложения разработчикам пришлось

    196
    Глава 4. Структурные паттерны бы самостоятельно выяснять, какой реализацией пользоваться в конкретных условиях.
    Чтобы избавить разработчика от этой ответственности, AppKit предостав- ляет мост
    NXImage
    /
    NXImageRep
    . Класс
    NXImage определяет интерфейс для обработки изображений. Реализация же определена в отдельной иерархии классов
    NXImageRep
    , в которой есть такие подклассы, как
    NXEPSImageRep
    ,
    NXCachedImageRep и
    NXBitMapImageRep
    . В классе
    NXImage хранятся ссылки на один или более объектов
    NXImageRep
    . Если имеется более одной реализации изображения, то
    NXImage выбирает самую подходящую для данного дисплея.
    При необходимости
    NXImage даже может преобразовать изображение из од- ного формата в другой. Интересная особенность этого варианта моста в том, что
    NXImage может одновременно хранить несколько реализаций
    NXImageRep
    Родственные паттерны
    Паттерн абстрактная фабрика (113) может создать и сконфигурировать мост.
    Для обеспечения совместной работы не связанных между собой классов прежде всего предназначен паттерн адаптер (146). Обычно он применя- ется в уже готовых системах. Мост же участвует в проекте с самого начала и призван поддержать возможность независимого изменения абстракций и их реализаций.
    ПАТТЕРН COMPOSITE (КОМПОНОВЩИК)
    Название и классификация паттерна
    Компоновщик — паттерн, структурирующий объекты.
    Назначение
    Компонует объекты в древовидные структуры для представления иерархий
    «часть — целое». Позволяет клиентам единообразно трактовать индивиду- альные и составные объекты.
    Мотивация
    Такие приложения, как графические редакторы и редакторы электрических схем, позволяют пользователям строить сложные схемы из более простых компонентов. Проектировщик может сгруппировать мелкие компоненты для формирования более крупных, которые, в свою очередь, могут стать ос-

    Паттерн Composite (компоновщик)
    197
    новой для создания еще более крупных. В простой реализации можно было бы определить классы графических примитивов, например текста и линий, а также классы, используемые в качестве контейнеров для этих примитивов.
    Но у такого решения есть существенный недостаток. Программа, в которой эти классы используются, должна по-разному обращаться с примитивами и контейнерами, хотя пользователь чаще всего работает с ними единообраз- но. Необходимость различать эти объекты усложняет приложение. Паттерн компоновщик описывает, как можно применить рекурсивную композицию таким образом, что клиенту не придется проводить различие между про- стыми и составными объектами.
    Graphic
    Draw()
    Add(Graphic)
    Remove(Graphic)
    GetChild(int)
    Line
    Draw()
    Text
    Draw()
    Picture
    Draw()
    Add(Graphic g)
    Remove(Graphic)
    GetChild(int)
    Rectangle
    Draw()
    Для всех графических объектов g g.Draw()
    добавить g в список графических объектов
    Графические объекты
    Ключом к паттерну компоновщик является абстрактный класс, который представляет одновременно и примитивы, и контейнеры. В графической системе этот класс может называться
    Graphic
    . В нем объявляются операции, специфичные для каждого вида графического объекта (такие как
    Draw
    ), а также общие для всех составных объектов, например операции для доступа и управления потомками.
    Подклассы
    Line
    ,
    Rectangle и
    Text
    (см. схему выше) определяют примитив- ные графические объекты. В них операция
    Draw реализована соответственно для рисования прямых, прямоугольников и текста. Поскольку у примитив- ных объектов нет потомков, то ни один из этих подклассов не реализует операции, относящиеся к управлению потомками.
    Класс
    Picture определяет агрегат, состоящий из объектов
    Graphic
    . Реализо- ванная в нем операция
    Draw вызывает одноименную функцию для каждого

    198
    Глава 4. Структурные паттерны потомка, а операции для работы с потомками уже не пусты. Поскольку интерфейс класса
    Picture соответствует интерфейсу
    Graphic
    , то в состав объекта
    Picture могут входить и другие такие же объекты.
    На следующей схеме показана типичная структура составного объекта, рекурсивно скомпонованного из объектов класса
    Graphic
    1   ...   12   13   14   15   16   17   18   19   ...   38


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