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

  • Glyph

  • ПАТТЕРН STRATEGY (СТРАТЕГИЯ)

  • ПРОЗРАЧНОЕ ОКРУЖЕНИЕ

  • ПАТТЕРН DECORATOR (ДЕКОРАТОР)

  • АБСТРАГИРОВАНИЕ СОЗДАНИЯ ОБЪЕКТА

  • ФАБРИКИ И ИЗГОТОВЛЕННЫЕ КЛАССЫ

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


    Скачать 6.37 Mb.
    НазваниеЭ. Гамма, Р. Хелм
    АнкорFactorial
    Дата14.03.2022
    Размер6.37 Mb.
    Формат файлаpdf
    Имя файлаPatterny_Obektno-Orientirovannogo_Proektirovania_2020.pdf
    ТипДокументы
    #395452
    страница6 из 38
    1   2   3   4   5   6   7   8   9   ...   38
    КЛАССЫ COMPOSITOR И COMPOSITION
    Мы определим класс
    Compositor
    (композитор) для объектов, которые мо- гут инкапсулировать алгоритм форматирования. Интерфейс (см. табл. 2.2) позволяет объекту этого класса узнать, какие глифы надо форматировать и когда. Форматируемые композитором глифы являются потомками специ- ального подкласса класса
    Glyph
    , который называется
    Composition
    (компо- зиция). Композиция при создании получает объект некоторого подкласса

    2.3.
    Форматирование
    67
    Compositor
    (специализированный для конкретного алгоритма разбиения на строки) и в нужные моменты предписывает композитору строить ком- позицию глифов по мере изменения документа пользователем. На рис. 2.5 изображены отношения между классами
    Composition и
    Compositor
    Glyph
    Insert(Glyph, int)
    Compositor
    Compose()
    SetComposition()
    Composition
    Insert(Glyph g, int i)
    ArrayCompositor
    Compose()
    TeXCompositor
    Compose()
    SimpleCompositor
    Compose()
    Композитор
    Потомки
    Композиция
    Glyph::Insert(g, i)
    compositor.Compose()
    Рис. 2.5. Отношения классов Composition и Compositor
    Неформатированный объект
    Composition содержит только видимые глифы, составляющие основное содержание документа. В нем нет глифов, опреде- ляющих физическую структуру документа, например
    Row и
    Column
    . В таком состоянии композиция находится сразу после создания и инициализации глифами, которые должна отформатировать. Во время форматирования ком- позиция вызывает операцию
    Compose своего объекта
    Compositor
    . Композитор обходит всех потомков композиции и вставляет новые глифы
    Row и
    Column в соответствии со своим алгоритмом разбиения на строки
    1
    . На рис. 2.6 пока- зана получающаяся объектная структура. Глифы, созданные и вставленные в эту структуру композитором, закрашены на рисунке серым цветом.
    Каждый подкласс класса
    Compositor может реализовывать свой собственный алгоритм разбиения на строки. Например, класс
    SimpleCompositor мог бы осуществлять быстрый проход, не обращая внимания на такую экзотику, как
    1
    Композитор должен получить коды символов глифов Character, чтобы вычислить места разбиения на строки. В разделе 2.8 мы увидим, как получить эту информацию полиморфно, не добавляя специфичной для символов операции к интерфейсу клас- са Glyph.

    68
    Глава 2. Практический пример: проектирование редактора документов
    Колонка
    Строка
    Строка
    G g
    Пробел
    Композиция
    Композитор
    Глифы,
    генерируемые композитором
    Рис. 2.6. Объектная структура, отражающая алгоритм разбиения на строки, выбираемый композитором
    «цвет» документа. Под «хорошим цветом» понимается равномерное рас- пределение текста и пустого пространства. Класс
    TeXCompositor мог бы реализовывать полный алгоритм TeX [Knu84], учитывающий наряду со многими другими вещами и цвет, но за счет увеличения времени форма- тирования.
    Наличие классов
    Compositor и
    Composition позволяет отделить код, под- держивающий физическую структуру документа, от кода алгоритмов фор- матирования. Мы можем добавить новые подклассы для класса
    Compositor
    , не трогая классов глифов, и наоборот. Фактически для замены алгоритма разбиения на строки во время выполнения достаточно добавить единствен- ную операцию
    SetCompositor к базовому интерфейсу класса
    Composition
    ПАТТЕРН STRATEGY (СТРАТЕГИЯ)
    Инкапсуляция алгоритма в объект — это назначение паттерна страте- гия (362). Основными участниками паттерна являются объекты-стратегии, инкапсулирующие различные алгоритмы, и контекст, в котором они рабо- тают. Композиторы представляют варианты стратегий; они инкапсулируют алгоритмы форматирования. Композиция образует контекст для стратегии композитора.
    Ключ к применению паттерна стратегия — проектирование интерфейсов стра- тегии и контекста, достаточно общих для поддержки широкого диапазона

    2.4. Оформление пользовательского интерфейса
    69
    алгоритмов. Поддержка нового алгоритма не должна требовать изменения интерфейса стратегии или контекста. В нашем примере поддержка доступа к потомкам, их вставки и удаления, в базовом интерфейсе класса
    Glyph имеет достаточно общий характер, чтобы подклассы класса
    Compositor могли из- менять физическую структуру документа независимо от того, с помощью каких алгоритмов это делается. Аналогичным образом интерфейс класса
    Compositor предоставляет композициям все, что им необходимо для запуска операции форматирования.
    2.4. ОФОРМЛЕНИЕ ПОЛЬЗОВАТЕЛЬСКОГО ИНТЕРФЕЙСА
    Рассмотрим два усовершенствования пользовательского интерфейса Lexi.
    Первое добавляет рамку вокруг области редактирования текста, чтобы четко обозначить границы страницы текста, второе — полосы прокрутки, при по- мощи которых пользователь просматривает разные части страницы. Чтобы упростить добавление и удаление таких элементов оформления (особенно во время выполнения), для их включения в пользовательский интерфейс не должно использоваться наследование. Максимальная гибкость достигается в том случае, если другим объектам пользовательского интерфейса даже не будет известно о том, какие еще элементы оформления в нем присутствуют.
    Это позволит добавлять и удалять декоративные элементы без изменения других классов.
    ПРОЗРАЧНОЕ ОКРУЖЕНИЕ
    В программировании улучшение пользовательского интерфейса подразуме- вает расширение существующего кода. Применение для этой цели наследо- вания не дает возможности реорганизовать интерфейс во время выполнения.
    Не менее серьезной проблемой является комбинаторный рост числа классов в случае широкого использования наследования.
    Можно было бы добавить рамку к классу
    Composition
    , породив от него но- вый подкласс
    BorderedComposition
    . Точно так же можно было бы добавить и интерфейс прокрутки, породив подкласс
    ScrollableComposition
    . Если же мы хотим иметь и рамку, и полосу прокрутки, следовало бы создать под- класс
    BorderedScrollableComposition
    , и так далее. Если довести эту идею до логического завершения, то пришлось бы создавать отдельный подкласс для каждой возможной комбинации декоративных элементов. Это решение быстро перестает работать с ростом количества таких декораций.

    70
    Глава 2. Практический пример: проектирование редактора документов
    Композиция объектов предоставляет куда более приемлемый и гибкий ме- ханизм расширения. Но из каких объектов формировать композицию? По- скольку известно, что мы оформляем существующий глиф, то и сам элемент оформления могли бы сделать объектом (скажем, экземпляром класса
    Border
    ).
    Следовательно, композиция может быть составлена из глифа и рамки. На следующем шаге необходимо решить, что во что включается. Можно считать, что рамка содержит глиф, и это разумно, так как рамка окружает глиф на экра- не. Можно принять и противоположное решение — поместить рамку внутрь глифа, но тогда пришлось бы модифицировать соответствующий подкласс класса
    Glyph
    , чтобы он «знал» о существовании рамки. Первый вариант — включение глифа в рамку — позволяет поместить весь код для отображения рамки в классе
    Border
    , оставив остальные классы без изменения.
    Как выглядит класс
    Border
    ? Тот факт, что у рамки есть визуальное пред- ставление, наталкивает на мысль, что она должна быть глифом, то есть подклассом класса
    Glyph
    . Но есть и более убедительные причины поступить именно так: клиентов не должно интересовать, есть у глифов рамки или нет.
    Все глифы должны обрабатываться единообразно. Когда клиент приказывает простому глифу без рамки нарисовать себя, тот делает это, не добавляя ника- ких элементов оформления. Если же этот глиф заключен в рамку, то клиент не должен как-то специально обрабатывать рамку; он просто предписывает составному глифу выполнить прорисовку точно так же, как и простому глифу в предыдущем случае. Отсюда следует, что интерфейс класса
    Border должен соответствовать интерфейсу класса
    Glyph
    . Чтобы гарантировать это, мы и делаем
    Border подклассом
    Glyph
    Все это подводит нас к идее прозрачного окружения (transparent enclosure), которая объединяет концепции: (1) композиции с одним потомком (одно- компонентные), и (2) совместимых интерфейсов. В общем случае клиенту неизвестно, имеет ли он дело с компонентом или его окружением (то есть родителем), особенно если окружение просто делегирует все операции своему единственному компоненту. Но окружение может также расширять поведение компонента, выполняя дополнительные действия либо до, либо после делегирования (а возможно, и до, и после). Окружение может также добавить компоненту состояние — как именно, будет показано ниже.
    МОНОГЛИФ
    Концепцию прозрачного окружения можно применить ко всем глифам, оформляющим другие глифы. Чтобы конкретизировать эту идею, определим подкласс класса
    Glyph
    , называемый
    MonoGlyph
    . Он будет выступать в роли аб-

    2.4. Оформление пользовательского интерфейса
    71
    страктного класса для глифов-декораций вроде рамки (см. рис. 2.7). В классе
    MonoGlyph хранится ссылка на компонент, которому он и переадресует все запросы. При этом
    MonoGlyph по определению становится абсолютно про- зрачным для клиентов.
    Вот как моноглиф реализует операцию
    Draw
    :
    void MonoGlyph::Draw (Window* w) {
    _component->Draw(w);
    }
    Glyph
    Draw(Window)
    MonoGlyph
    Draw(Window)
    Scroller
    Draw(Window)
    Border
    Draw(Window)
    DrawBorder(Window)
    Компонент
    Рис. 2.7. Отношения класса MonoGlyph с другими классами
    Подклассы
    MonoGlyph замещают по меньшей мере одну из таких операций переадресации. Например,
    Border::Draw сначала вызывает операцию роди- тельского класса
    MonoGlyph::Draw
    , чтобы компонент выполнил свою часть работы, то есть нарисовал все, кроме рамки. Затем
    Border::Draw рисует рамку вызовом своей закрытой операции
    DrawBorder
    , детали которой мы опустим:
    void Border::Draw (Window* w) {
    MonoGlyph::Draw(w);
    DrawBorder(w);
    }
    Обратите внимание, что
    Border::Draw
    , по сути дела, расширяет операцию родительского класса, чтобы нарисовать рамку. Это не то же самое, что про- стая замена операции: в таком случае
    MonoGlyph::Draw не вызывалась бы.

    72
    Глава 2. Практический пример: проектирование редактора документов
    На рис. 2.7 показан другой подкласс класса
    MonoGlyph
    Scroller
    — это
    MonoGlyph
    , который рисует свои компоненты на экране в зависимости от положения двух полос прокрутки, добавляющихся в качестве элементов оформления. Когда
    Scroller отображает свой компонент, он приказывает своей графической системе обрезать его по границам окна. Отсеченные части компонента, ока- завшиеся за пределами видимой части окна, не появляются на экране.
    Теперь у нас есть все, что необходимо для добавления рамки и интерфейса прокрутки к области редактирования текста в Lexi. Мы помещаем имею- щийся экземпляр класса
    Composition в экземпляр класса
    Scroller
    , чтобы добавить интерфейс прокрутки, а результат композиции еще раз погружаем в экземпляр класса
    Border
    . Получившийся объект показан на рис. 2.8.
    Колонка
    Строка
    Строка
    G g
    Пробел
    Композиция
    Полоса прокрутки
    Рамка
    Рис. 2.8. Объектная структура после добавления элементов оформления
    Обратите внимание, что с таким же успехом можно было бы использовать обратный порядок композиции, сначала добавив рамку, а потом поместив ре- зультат в
    Scroller
    . В таком случае рамка прокручивалась бы вместе с текстом.
    Может быть, это именно то, что вам нужно, а может, и нет. Здесь важно то, что прозрачное окружение легко позволяет клиенту экспериментировать с раз- ными вариантами без знания подробностей кода, добавляющего декорации.

    2.5. Поддержка нескольких стандартов оформления
    73
    Отметим, что рамка допускает композицию не более чем с одним глифом.
    Этим она отличается от рассмотренных выше композиций, где родительско- му объекту позволялось иметь сколько угодно потомков. Здесь же заключе- ние чего-то в рамку предполагает, что это «что-то» имеется в единственном экземпляре. Мы могли бы приписать некоторую семантику декорации более одного объекта, но тогда пришлось бы вводить множество видов композиций с оформлением: оформление строки, колонки и т. д. Это не улучшит архи- тектуру, так как у нас уже есть классы для такого рода композиций. Поэтому для композиции лучше использовать уже существующие классы, а новые до- бавлять для оформления результата. Отделение декорации от других видов композиции одновременно упрощает классы, реализующие разные элементы оформления, и уменьшает их количество. Кроме того, мы избавляемся от необходимости дублировать уже имеющуюся функциональность.
    ПАТТЕРН DECORATOR (ДЕКОРАТОР)
    Паттерн декоратор (209) абстрагирует отношения между классами и объ- ектами, необходимые для поддержки оформления с помощью техники прозрачного окружения. Термин «оформление» на самом деле применяется в более широком смысле, чем мы видели выше. В паттерне декоратор под ним понимается нечто, расширяющее круг обязанностей объекта. Можно, например, представить себе оформление абстрактного дерева синтаксиче- ского разбора семантическими действиями, конечного автомата — новыми состояниями или сети, состоящей из устойчивых объектов, — тегами атри- бутов. Декоратор обобщает подход, который мы использовали в Lexi, чтобы расширить его область применения.
    2.5. ПОДДЕРЖКА НЕСКОЛЬКИХ СТАНДАРТОВ ОФОРМЛЕНИЯ
    При проектировании системы приходится сталкиваться с проблемой пере- носимости между различными программными и аппаратными платформа- ми. Перенос Lexi на другую платформу не должен требовать капитального перепроектирования, иначе не стоит за него и браться. Он должен быть максимально прост.
    Одним из препятствий для переноса является разнообразие стандартов оформления, призванных унифицировать работу с приложениями на данной платформе. Эти стандарты определяют, как приложения должны выглядеть и реагировать на действия пользователя. Хотя существующие стандарты не так уж сильно отличаются друг от друга, ни один пользователь не спутает

    74
    Глава 2. Практический пример: проектирование редактора документов один стандарт с другим — приложения для Motif выглядят не совсем так, как аналогичные приложения на других платформах, и наоборот. Программа, работающая более чем на одной платформе, на всех платформах должна со- ответствовать принятой стилистике пользовательского интерфейса.
    Одна из целей проектирования — сделать так, чтобы Lexi поддерживал разные стандарты внешнего облика и чтобы легко можно было добавить поддержку нового стандарта сразу же после его появления (а это неизбежно произойдет). Хотелось бы также, чтобы наш дизайн решал и другую задачу: изменение оформления Lexi во время выполнения.
    АБСТРАГИРОВАНИЕ СОЗДАНИЯ ОБЪЕКТА
    Все, что мы видим и с чем можем взаимодействовать в пользовательском интерфейсе Lexi, — это визуальные глифы, скомпонованные в другие, уже невидимые глифы вроде строки (
    Row
    ) и колонки (
    Column
    ). Невидимые глифы объединяют видимые — скажем, кнопку (
    Button
    ) или символ (
    Character
    ) — и правильно располагают их на экране. В стилевых руководствах много говорится о внешнем облике и поведении так называемых «виджетов»
    (widgets); это просто другое название таких видимых глифов, как кнопки, полосы прокрутки и меню, выполняющих в пользовательском интерфейсе функции элементов управления. Для представления данных виджеты могут пользоваться более простыми глифами: символами, окружностями, прямо- угольниками и многоугольниками.
    Будем считать, что классы глифов-виджетов, с помощью которых реализу- ются стандарты оформления, делятся на два класса:
    „
    „
    набор абстрактных подклассов класса
    Glyph для каждой категории вид- жетов. Например, абстрактный класс
    ScrollBar будет дополнять ин- терфейс глифа с целью получения операций прокрутки общего вида, а
    Button
    — это абстрактный класс, добавляющий операции с кнопками;
    „
    „
    набор конкретных подклассов для каждого абстрактного подкласса, в которых реализованы стандарты внешнего облика. Так, у
    ScrollBar могут быть подклассы
    MotifScrollBar и
    PMScrollBar
    , реализующие по- лосы прокрутки в стиле Motif и Presentation Manager соответственно.
    Lexi должен различать глифы-виджеты для разных стилей внешнего оформ- ления. Например, когда необходимо поместить в интерфейс кнопку, редактор должен создать экземпляр подкласса класса
    Glyph для нужного стиля кнопки
    (
    MotifButton
    ,
    PMButton
    ,
    MacButton и т. д.).

    2.5. Поддержка нескольких стандартов оформления
    75
    Ясно, что в реализации Lexi это нельзя сделать непосредственно — напри- мер, вызовом конструктора, если речь идет о языке C++. При этом была бы жестко запрограммирована кнопка одного конкретного стиля, а значит, вы- брать нужный стиль во время выполнения оказалось бы невозможно. Кроме того, мы были бы вынуждены отслеживать и изменять каждый такой вызов конструктора при переносе Lexi на другую платформу. А ведь кнопки — это лишь один элемент пользовательского интерфейса Lexi. Загромождение кода вызовами конструкторов для разных классов оформления создает существенные неудобства при сопровождении. Стоит что-нибудь пропу- стить — и в приложении для Mac появится меню в стиле Motif.
    Lexi необходимо каким-то образом определить нужный стандарт оформле- ния для создания подходящих виджетов. При этом надо не только постарать- ся избежать явных вызовов конструкторов, но и уметь без труда заменять весь набор виджетов. Этого можно добиться путем абстрагирования процесса
    создания объекта. Следующий пример пояснит, что имеется в виду.
    ФАБРИКИ И ИЗГОТОВЛЕННЫЕ КЛАССЫ
    В обычном случае экземпляр глифа полосы прокрутки в стиле Motif созда- ется следующим кодом на C++:
    ScrollBar* sb = new MotifScrollBar;
    Но если вы хотите свести к минимуму зависимость Lexi от стандарта оформ- ления, именно такого кода следует избегать. Предположим, однако, что sb инициализируется так:
    ScrollBar* sb = guiFactory->CreateScrollBar(); где guiFactory
    — экземпляр класса
    MotifFactory
    . Операция
    CreateScrollBar()
    возвращает новый экземпляр подходящего подкласса
    ScrollBar
    , который со- ответствует нужному варианту оформления, в данном случае Motif. С точки зрения клиентов результат тот же, что и при прямом вызове конструктора
    MotifScrollBar
    . Но есть и существенное отличие: нигде в коде больше не упоминается имя Motif. Объект guiFactory абстрагирует процесс создания полос прокрутки не только для Motif, но и для любых стандартов оформ- ления. Более того, guiFactory не ограничивается изготовлением только полос прокрутки и может применяться для производства любых виджетов, включая кнопки, поля ввода, меню и т. д.
    Все это возможно благодаря тому, что
    MotifFactory является подклассом
    GUIFactory
    — абстрактного класса, который определяет общий интерфейс для

    1   2   3   4   5   6   7   8   9   ...   38


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