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

  • VisualComponent

  • Component

  • ConcreteComponent

  • Decorator

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


    Скачать 6.37 Mb.
    НазваниеЭ. Гамма, Р. Хелм
    АнкорFactorial
    Дата14.03.2022
    Размер6.37 Mb.
    Формат файлаpdf
    Имя файлаPatterny_Obektno-Orientirovannogo_Proektirovania_2020.pdf
    ТипДокументы
    #395452
    страница18 из 38
    1   ...   14   15   16   17   18   19   20   21   ...   38
    208
    Глава 4. Структурные паттерны
    Аналогичным образом можно определить и другие контейнеры для обо- рудования, например
    Cabinet
    (корпус) и
    Bus
    (шина). Этого вполне доста- точно для сборки из отдельных блоков (довольно простого) персонального компьютера:
    Cabinet* cabinet = new Cabinet("PC Cabinet");
    Chassis* chassis = new Chassis("PC Chassis");
    cabinet->Add(chassis);
    Bus* bus = new Bus("MCA Bus");
    bus->Add(new Card("16Mbs Token Ring"));
    chassis->Add(bus);
    chassis->Add(new FloppyDisk("3.5in Floppy"));
    cout << "The net price is " << chassis->NetPrice() << endl;
    Известные применения
    Примеры паттерна компоновщик встречаются почти во всех объектно-ориен- тированных системах. Первоначально класс
    View в схеме модель/представле- ние/контроллер в языке Smalltalk [KP88] был компоновщиком, и почти все библиотеки для построения пользовательских интерфейсов и каркасы про- ектировались аналогично. Среди них ET++ (со своей библиотекой VObjects
    [WGM88]) и InterViews (классы Styles [LCI+92], Graphics [VL88] и Glyphs
    [CL90].) Интересно отметить, что первоначально вид
    View имел несколько подвидов, то есть он был одновременно и классом
    Component
    , и классом
    Composite
    . В версии 4.0 языка Smalltalk-80 схема модель/представление/кон- троллер была пересмотрена, в нее ввели класс
    VisualComponent
    , подклассами которого являлись
    View и
    CompositeView
    В каркасе для построения компиляторов RTL, который написан на Smalltalk
    [JML92], паттерн компоновщик используется очень широко.
    RTLExpression
    — это разновидность класса
    Component для построения деревьев синтаксическо- го разбора. У него есть подклассы, например
    BinaryExpression
    , потомками которого являются объекты класса
    RTLExpression
    . В совокупности эти классы определяют составную структуру для деревьев разбора.
    RegisterTransfer
    — класс
    Component для промежуточной формы представления программы SSA
    (Single Static Assignment). Листовые подклассы
    RegisterTransfer опреде- ляют различные статические присваивания, например:
    „
    „
    примитивные присваивания, которые выполняют операцию над двумя регистрами и сохраняют результат в третьем;

    Паттерн Decorator (декоратор)
    209
    „
    „
    присваивание, у которого есть исходный, но нет целевого регистра. Сле- довательно, регистр используется после возврата из процедуры;
    „
    „
    присваивание, у которого есть целевой, но нет исходного регистра. Это означает, что присваивание регистру происходит перед началом про- цедуры.
    Подкласс
    RegisterTransferSet является примером класса
    Composite для представления присваиваний, изменяющих сразу несколько регистров.
    Другой пример применения паттерна компоновщик — финансовые про- граммы, когда инвестиционный портфель состоит из нескольких отдельных активов. Можно поддержать сложные агрегаты активов, если реализовать портфель в виде компоновщика, согласованного с интерфейсом каждого актива [BE93].
    Паттерн команда (275) описывает возможности компоновки и упорядоче- ния объектов
    Command с помощью класса компоновщика
    MacroCommand
    Родственные паттерны
    Отношение «компонент — родитель» используется в паттерне цепочка обя- занностей (263).
    Паттерн декоратор часто применяется совместно с компоновщиком. Ког- да декораторы и компоновщики используются вместе, у них обычно бывает общий родительский класс. Поэтому декораторам придется под- держать интерфейс компонентов такими операциями, как
    Add
    ,
    Remove и
    GetChild
    Паттерн приспособленец (231) позволяет совместно использовать компо- ненты, но ссылаться на своих родителей они уже не могут.
    Итератор (302) можно использовать для обхода составных объектов.
    Посетитель (379) локализует операции и поведение, которые в противном случае пришлось бы распределять между классами
    Composite и
    Leaf
    ПАТТЕРН DECORATOR (ДЕКОРАТОР)
    Название и классификация паттерна
    Декоратор — паттерн, структурирующий объекты.

    210
    Глава 4. Структурные паттерны
    Назначение
    Динамически добавляет объекту новые обязанности. Является гибкой аль- тернативой порождению подклассов с целью расширения функциональности.
    Другие названия
    Wrapper
    (обертка).
    Мотивация
    Иногда бывает нужно возложить дополнительные обязанности на отдельный объект, а не на класс в целом. Так, библиотека для построения графических интерфейсов пользователя должна «уметь» добавлять новое свойство, скажем, рамку или новое поведение (например, возможность прокрутки к любому элементу интерфейса).
    Новые обязанности можно добавить с помощью наследования. При на- следовании классу с рамкой вокруг каждого экземпляра подкласса будет рисоваться рамка. Однако такое решение недостаточно гибкое из-за того, что рамка будет выбираться статически. Клиент не может управлять тем, когда и как компоненты будут декорироваться обрамлением.
    Другое, более гибкое решение — поместить компонент в другой объект, назы- ваемый декоратором, который как раз и добавляет рамку. Декоратор следует интерфейсу декорируемого объекта, поэтому его присутствие прозрачно для клиентов компонента. Декоратор переадресует запросы внутреннему компо- ненту, но может выполнять и дополнительные действия (например, рисовать рамку) до или после переадресации. Поскольку декораторы прозрачны, они могут вкладываться друг в друга, добавляя тем самым неограниченное число новых обязанностей.

    Паттерн Decorator (декоратор)
    211
    Предположим, что имеется объект класса
    TextView
    , который отображает текст в окне. По умолчанию
    TextView не имеет полос прокрутки, поскольку они не всегда нужны. Но при необходимости их удастся добавить с помощью декоратора
    ScrollDecorator
    . Допустим, что еще мы хотим добавить жирную сплошную рамку вокруг объекта
    TextView
    . Здесь может помочь декоратор
    BorderDecorator
    . Мы просто компонуем оба декоратора с
    BorderDecorator для получения искомого результата.
    Ниже на схеме показано, как композиция объекта
    TextView с объектами
    BorderDecorator и
    ScrollDecorator формирует элемент для ввода текста, окруженный рамкой и снабженный полосой прокрутки:
    aBorderDecorator
    компонент
    aScrollDecorator
    компонент
    aTextView
    Классы
    ScrollDecorator и
    BorderDecorator являются подклассами
    Decorator
    — абстрактного класса, который представляет визуальные ком- поненты, применяемые для оформления других визуальных компонентов.
    VisualComponent
    — это абстрактный класс для представления визуальных объектов. В нем определен интерфейс для рисования и обработки событий.
    Отметим, что класс
    Decorator просто переадресует запросы на рисование своему компоненту, а его подклассы могут расширять эту операцию.
    VisualComponent
    Draw()
    TextView
    Draw()
    Decorator
    Draw()
    ScrollDecorator
    Draw()
    ScrollTo()
    BorderDecorator
    Draw()
    DrawBorder()
    scrollPosition borderWidth component>Draw()
    Decorator::Draw();
    DrawBorder();
    component

    212
    Глава 4. Структурные паттерны
    Подклассы
    Decorator могут добавлять любые операции для обеспече- ния необходимой функциональности. Так, операция
    ScrollTo объекта
    ScrollDecorator позволяет другим объектам выполнять прокрутку, если им известно о присутствии объекта
    ScrollDecorator в интерфейсе. Важная осо- бенность этого паттерна состоит в том, что декораторы могут употребляться везде, где возможно появление самого объекта
    VisualComponent
    . При этом клиент не может отличить декорированный объект от недекорированного, а значит, и никоим образом не зависит от наличия или отсутствия декора- тивных элементов.
    Применимость
    Основные условия для применения паттерна декоратор:
    „
    „
    динамическое, прозрачное для клиентов добавление обязанностей объ- ектам (не затрагивающее другие объекты);
    „
    „
    реализация обязанностей, которые могут быть сняты с объекта;
    „
    „
    расширение путем порождения подклассов по каким-то причинам не- удобно или невозможно. Иногда приходится реализовывать много не- зависимых расширений, так что порождение подклассов для поддержки всех возможных комбинаций приведет к стремительному росту их чис- ла. В других случаях определение класса может быть скрыто или по- чему-либо еще недоступно, так что породить от него подкласс нельзя.
    Структура
    Component
    Operation()
    ConcreteComponent
    Operation()
    Decorator
    Operation()
    ConcreteDecoratorA
    Operation()
    addedState
    ConcreteDecoratorB
    Operation()
    AddedBehavior()
    component >Operation()
    Decorator::Operation();
    AddedBehavior();
    component

    Паттерн Decorator (декоратор)
    213
    Участники
    „
    „
    Component (
    VisualComponent
    ) — компонент:
    • определяет интерфейс для объектов, на которые могут быть динами- чески возложены дополнительные обязанности;
    „
    „
    ConcreteComponent (
    TextView
    ) — конкретный компонент:
    • определяет объект, на который возлагаются дополнительные обязан- ности;
    „
    „
    Decorator — декоратор:
    • хранит ссылку на объект
    Component и определяет интерфейс, соответ- ствующий интерфейсу
    Component
    ;
    „
    „
    ConcreteDecorator (
    BorderDecorator
    ,
    ScrollDecorator
    ) — конкретный декоратор:
    • возлагает дополнительные обязанности на компонент.
    Отношения
    Decorator переадресует запросы объекту
    Component
    . Может выполнять и до- полнительные операции до и после переадресации.
    Результаты
    У паттерна декоратор есть, по крайней мере, два плюса и два минуса:
    „
    „
    большая гибкость, нежели у статического наследования. Паттерн де- коратор позволяет более гибко добавлять объекту новые обязанно- сти, чем было бы возможно в случае статического (множественного) наследования. Декоратор может добавлять и удалять обязанности во время выполнения программы. С другой стороны, при использова- нии наследования требуется создавать новый класс для каждой до- полнительной обязанности (например,
    BorderedScrollableTextView
    ,
    BorderedTextView
    ), что ведет к увеличению числа классов и, как след- ствие, к возрастанию сложности системы. Кроме того, применение не- скольких декораторов к одному компоненту позволяет формировать произвольные комбинации обязанностей.
    Декораторы также позволяют легко добавить одно и то же свойство дваж- ды. Например, чтобы окружить объект
    TextView двойной рамкой, нужно просто добавить два декоратора
    BorderDecorators
    . Двойное наследование классу
    Border в лучшем случае чревато ошибками;

    214
    Глава 4. Структурные паттерны
    „
    „
    позволяет избежать перегруженных функциями классов на верхних уров-
    нях иерархии. Декоратор разрешает добавлять новые обязанности по мере необходимости. Вместо того чтобы пытаться поддержать все мыслимые возможности в одном сложном, допускающем разностороннюю настрой- ку классе, вы можете определить простой класс и постепенно наращивать его функциональность с помощью декораторов. В результате приложе- ние уже не перегружается неиспользуемыми функциями. Нетрудно так- же определять новые виды декораторов независимо от классов, которые они расширяют, даже если первоначально такие расширения не планиро- вались. При расширении же сложного класса обычно приходится вникать в детали, не имеющие отношения к добавляемой функции;
    „
    „
    декоратор и его компонент не идентичны. Декоратор действует как про- зрачное обрамление. Но декорированный компонент все же не идентичен исходному. При использовании декораторов это следует иметь в виду;
    „
    „
    множество мелких объектов. При использовании в проекте паттерна де- коратор нередко формируется система, составленная из большого числа мелких объектов, похожих друг на друга. Такие объекты различаются только способом взаимосвязи, а не классом и не значениями своих вну- тренних переменных. Хотя такие системы легко настраиваются проек- тировщиком, хорошо разбирающимся в их строении, изучать и отлажи- вать их очень тяжело.
    Реализация
    При применении паттерна декоратор следует учитывать ряд аспектов:
    „
    „
    соответствие интерфейсов. Интерфейс декоратора должен соответ- ствовать интерфейсу декорируемого компонента. Поэтому классы
    ConcreteDecorator должны наследовать общему классу (по крайней мере, в C++);
    „
    „
    отсутствие абстрактного класса Decorator. Нет необходимости опреде- лять абстрактный класс
    Decorator
    , если вы собираетесь добавить всего одну обязанность. Так часто происходит, когда вы работаете с уже суще- ствующей иерархией классов, а не проектируете новую. В таком случае ответственность за переадресацию запросов, которую обычно несет класс
    Decorator
    , можно возложить непосредственно на
    ConcreteDecorator
    ;
    „
    „
    облегченные классы Component. Чтобы можно было гарантировать со- ответствие интерфейсов, компоненты и декораторы должны наследо- вать общему классу
    Component
    . Важно, чтобы этот класс был настолько легким, насколько возможно. Иными словами, он должен определять

    Паттерн Decorator (декоратор)
    215
    интерфейс, а не хранить данные. Определение представления данных должно быть передано в подклассы; в противном случае декораторы мо- гут стать весьма тяжеловесными, и применять их в большом количестве будет накладно. Включение большого числа функций в класс
    Component также увеличивает вероятность, что конкретным подклассам придется платить за то, что им не нужно;
    „
    „
    изменение облика, а не внутреннего устройства объекта. Декоратор можно рассматривать как появившуюся у объекта оболочку, которая изменяет его поведение. Альтернатива — изменение внутреннего устройства объ- екта, хорошим примером чего может служить паттерн стратегия (362).
    Стратегии лучше подходят в ситуациях, когда класс
    Component уже достаточно тяжел, так что применение паттерна декоратор обходится слишком дорого. В паттерне стратегия компоненты передают часть своей функциональности отдельному объекту-стратегии, поэтому изменить или расширить поведение компонента допустимо, заменив этот объект.
    Например, мы можем поддержать разные стили рамок, поручив рисование рамки специальному объекту
    Border
    . Объект
    Border является примером объекта-стратегии: в данном случае он инкапсулирует стратегию рисова- ния рамки. Число стратегий может быть любым, поэтому эффект такой же, как от рекурсивной вложенности декораторов.
    Например, в системах MacApp 3.0 [App89] и Bedrock [Sym93a] графические компоненты, называемые представлениями (views), хранят список объек- тов-оформителей (adorner), которые могут добавлять различные оформле- ния вроде границ к виду. Если к представлению присоединены такие объ- екты, он дает им возможность выполнить свои функции. MacApp и Bedrock вынуждены предоставить доступ к этим операциям, поскольку класс
    View весьма тяжеловесен. Было бы слишком расточительно использовать полно- масштабный объект этого класса только для того, чтобы добавить рамку.
    Поскольку паттерн декоратор изменяет лишь внешний облик компонента, последнему ничего не надо «знать» о своих декораторах, то есть декора- торы прозрачны для компонента.
    aDecorator
    component
    aDecorator
    component
    aComponent
    Функциональность, расширенная декоратором

    216
    Глава 4. Структурные паттерны
    В случае стратегий самому компоненту известно о возможных расшире- ниях. Поэтому он должен располагать информацией обо всех стратегиях и ссылаться на них.
    aComponent
    strategies
    aStrategy
    next next
    aStrategy
    Функциональность, расширенная стратегией
    При использовании подхода, основанного на стратегиях, может возник- нуть необходимость в модификации компонента, чтобы он соответствовал новому расширению. С другой стороны, у стратегии может быть свой собственный специализированный интерфейс, тогда как интерфейс де- коратора должен повторять интерфейс компонента. Например, стратегии рисования рамки необходимо определить всего лишь интерфейс для этой операции (
    DrawBorder
    ,
    GetWidth и т. д.), то есть класс стратегии может быть легким, несмотря на тяжеловесность компонента.
    Системы MacApp и Bedrock применяют такой подход не только для оформления представлений, но и для расширения особенностей по- ведения объектов, связанных с обработкой событий. В обеих системах представление ведет список объектов поведения, которые могут моди- фицировать и перехватывать события. Каждому зарегистрированному объекту поведения представление предоставляет возможность обработать событие до того, как оно будет передано незарегистрированным объектам такого рода, за счет чего достигается переопределение поведения. Можно, например, декорировать вид специальной поддержкой работы с клавиа- турой, если зарегистрировать объект поведения, который перехватывает и обрабатывает события нажатия клавиш.
    Пример кода
    Следующий пример показывает, как декораторы пользовательского интер- фейса реализуются в программе на C++. Предполагается, что класс компо- нента называется
    VisualComponent
    :
    class VisualComponent {
    public:
    VisualComponent()
    virtual void Draw();

    Паттерн Decorator (декоратор)
    217
    virtual void Resize();
    // ...
    };
    Определим подкласс класса
    VisualComponent с именем
    Decorator
    , от которого затем породим подклассы, реализующие различные оформления:
    class Decorator : public VisualComponent {
    public:
    Decorator(VisualComponent*);
    virtual void Draw();
    virtual void Resize();
    // ...
    private:
    VisualComponent* _component;
    };
    Объект класса
    Decorator декорирует объект
    VisualComponent
    , на который ссылается переменная экземпляра
    _component
    , инициализируемая в кон- структоре. Для каждой операции в интерфейсе
    VisualComponent в классе
    Decorator определена реализация по умолчанию, передающая запросы объ- екту, на который ведет ссылка
    _component
    :
    void Decorator::Draw () {
    _component->Draw();
    }
    void Decorator::Resize () {
    _component->Resize();
    }
    Подклассы
    Decorator определяют специализированные операции. Напри- мер, класс
    BorderDecorator добавляет к своему внутреннему компоненту рамку.
    BorderDecorator
    — это подкласс
    Decorator
    , где операция
    Draw заме- щена так, что рисует рамку. В этом классе определена также закрытая вспо- могательная операция
    DrawBorder
    , которая, собственно, и изображает рамку.
    Реализации всех остальных операций этот подкласс наследует от
    Decorator
    :
    class BorderDecorator : public Decorator {
    public:
    BorderDecorator(VisualComponent*, int borderWidth);
    virtual void Draw();
    private:
    void DrawBorder(int);

    1   ...   14   15   16   17   18   19   20   21   ...   38


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