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

  • Фасад Классы клиента

  • StackMachineCodeGenerator RISCCodeGenerator Stream Statement Node VariableNode ExpressionNode BytecodeStream CodeGenerator

  • Фасад Классы подсистемы

  • Классы подсистемы

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


    Скачать 6.37 Mb.
    НазваниеЭ. Гамма, Р. Хелм
    АнкорFactorial
    Дата14.03.2022
    Размер6.37 Mb.
    Формат файлаpdf
    Имя файлаPatterny_Obektno-Orientirovannogo_Proektirovania_2020.pdf
    ТипДокументы
    #395452
    страница19 из 38
    1   ...   15   16   17   18   19   20   21   22   ...   38

    218
    Глава 4. Структурные паттерны private:
    int _width;
    };
    void BorderDecorator::Draw () {
    Decorator::Draw();
    DrawBorder(_width);
    }
    Подклассы
    ScrollDecorator и
    DropShadowDecorator
    , которые добавят ви- зуальному компоненту возможность прокрутки и оттенения, реализуются аналогично.
    Теперь экземпляры этих классов можно скомпоновать для получения раз- личных оформлений. Ниже показано, как использовать декораторы для создания прокручиваемого компонента
    TextView с рамкой.
    Во-первых, нужно каким-то образом поместить визуальный компонент в оконный объект. Предположим, что в классе
    Window для этой цели имеется операция
    SetContents
    :
    void Window::SetContents (VisualComponent* contents) {
    // ...
    }
    Теперь можно создать поле для ввода текста и окно, в котором будет на- ходиться это поле:
    Window* window = new Window;
    TextView* textView = new TextView;
    TextView является подклассом
    VisualComponent
    , значит, мы могли бы по- местить его в окно:
    window->SetContents(textView);
    Но нам нужно поле ввода с рамкой и возможностью прокрутки, поэтому перед размещением в окне его необходимо соответствующим образом оформить:
    window->SetContents(
    new BorderDecorator(
    new ScrollDecorator(textView), 1
    )
    );

    Паттерн Decorator (декоратор)
    219
    Поскольку класс
    Window обращается к своему содержимому только через интерфейс
    VisualComponent
    , то ему неизвестно о присутствии декоратора.
    Клиент при желании может сохранить ссылку на само поле ввода, если ему нужно работать с ним непосредственно — например, вызывать операции, не входящие в интерфейс
    VisualComponent
    . Клиенты, которым важна идентич- ность объекта, также должны обращаться к нему напрямую.
    Известные применения
    Во многих библиотеках для построения объектно-ориентированных ин- терфейсов пользователя декораторы применяются для добавления к ви- джетам графических оформлений. В качестве примеров можно назвать
    InterViews [LVC89, LCI+92], ET++ [WGM88] и библиотеку классов
    ObjectWorks\Smalltalk [Par90]. Более экзотические варианты применения паттерна декоратор — это класс
    DebuggingGlyph из библиотеки InterViews и
    PassivityWrapper из ParcPlace Smalltalk.
    DebuggingGlyph выводит от- ладочную информацию до и после того, как переадресует запрос на раз- мещение своему компоненту. Эта информация может быть полезной для анализа и отладки стратегии размещения объектов в сложной композиции.
    Класс
    PassivityWrapper позволяет разрешить или запретить взаимодей- ствие компонента с пользователем.
    Но применение паттерна декоратор никоим образом не ограничивается гра- фическими интерфейсами пользователя, как показывает следующий пример, основанный на потоковых классах из каркаса ET++ [WGM88].
    Поток является фундаментальной абстракцией для большинства средств ввода/вывода. Он может предоставлять интерфейс для преобразования объ- ектов в последовательность байтов или символов. Это позволяет записать объект в файл или буфер в памяти и впоследствии извлечь его оттуда. Са- мый очевидный способ сделать это — определить абстрактный класс
    Stream с подклассами
    MemoryStream и
    FileStream
    . Предположим, однако, что вам также хотелось бы иметь возможность:
    „
    „
    сжимать данные в потоке с применением различных алгоритмов (коди- рование с переменной длиной строки, алгоритм Лемпеля — Зива и т. д.);
    „
    „
    преобразовывать данные в 7-битные символы кода ASCII для передачи по каналу связи.
    Паттерн декоратор позволяет весьма элегантно добавить такие обязан- ности потокам. На схеме ниже показано одно из возможных решений задачи.

    220
    Глава 4. Структурные паттерны
    Stream
    PutInt()
    PutString()
    HandleBufferFull()
    MemoryStream
    HandleBufferFull()
    FileStream
    HandleBufferFull()
    StreamDecorator
    HandleBufferFull()
    ASCII7Stream
    HandleBufferFull()
    CompressingStream
    HandleBufferFull()
    component>HandleBufferFull()
    Сжать данные в буфере
    StreamDecorator::HandleBufferFull()
    component
    Абстрактный класс
    Stream имеет внутренний буфер и предоставляет опера- ции для помещения данных в поток (PutInt, PutString). Как только буфер заполняется,
    Stream вызывает абстрактную операцию
    HandleBufferFull
    , которая выполняет реальное перемещение данных. В классе
    FileStream эта операция замещается так, что буфер записывается в файл.
    Ключевую роль здесь играет класс
    StreamDecorator
    . Именно в нем хранится ссылка на тот поток-компонент, которому переадресуются все запросы. Под- классы
    StreamDecorator замещают операцию
    HandleBufferFull и выполняют дополнительные действия, перед тем как вызвать реализацию этой операции в классе
    StreamDecorator
    Например, подкласс
    CompressingStream сжимает данные, а
    ASCII7Stream преобразует их в 7-битный код ASCII. Теперь, для того чтобы создать объект
    FileStream
    , который одновременно сжимает данные и преобразует результат в 7-битный код, достаточно просто декорировать
    FileStream с использова- нием
    CompressingStream и
    ASCII7Stream
    :
    Stream* aStream = new CompressingStream(
    new ASCII7Stream(
    new FileStream("aFileName")
    )
    );
    aStream->PutInt(12);
    aStream->PutString("aString");
    Родственные паттерны
    Адаптер (171): декоратор изменяет только обязанности объекта, не меняя интерфейса, а адаптер придает объекту совершенно новый интерфейс.

    Паттерн Facade (фасад)
    221
    Компоновщик (196): декоратор можно считать вырожденным случаем состав- ного объекта, у которого есть только один компонент. Однако декоратор до- бавляет новые обязанности, агрегирование объектов не является его целью.
    Стратегия (362): декоратор позволяет изменить внешний облик объекта, стратегия — его внутреннее содержание. Это два взаимодополняющих спо- соба изменения объекта.
    ПАТТЕРН FACADE (ФАСАД)
    Название и классификация паттерна
    Фасад — паттерн, структурирующий объекты.
    Назначение
    Предоставляет унифицированный интерфейс вместо набора интерфейсов некоторой подсистемы. Фасад определяет интерфейс более высокого уровня, который упрощает использование подсистемы.
    Мотивация
    Разбиение на подсистемы облегчает проектирование сложной системы в це- лом. Общая цель всякого проектирования — свести к минимуму зависимость подсистем друг от друга и обмен информацией между ними. Один из спосо- бов решения этой задачи — введение объекта фасад, который предоставляет единый упрощенный интерфейс к более сложным системным средствам.
    Фасад
    Классы клиента
    Классы подсистемы
    Рассмотрим, например, среду программирования, которая дает при- ложениям доступ к подсистеме компиляции. В эту подсистему входят классы, реализующие компилятор — такие как
    Scanner
    (лексический

    222
    Глава 4. Структурные паттерны анализатор),
    Parser
    (синтаксический анализатор),
    ProgramNode
    (узел про- граммы),
    BytecodeStream
    (поток байтовых кодов) и
    ProgramNodeBuilder
    (строитель узла программы). Некоторым специализированным прило- жениям, возможно, понадобится прямой доступ к этим классам. Но для большинства клиентов компилятора такие детали, как синтаксический разбор и генерирование кода, обычно не нужны; им просто требуется от- компилировать некоторую программу. Для таких клиентов применение мощного, но низкоуровневого интерфейса в подсистеме компиляции только усложняет задачу.
    Чтобы предоставить интерфейс более высокого уровня, изолирующий клиент от этих классов, в подсистему компиляции включен также класс
    Compiler
    (компилятор). Он определяет унифицированный интерфейс ко всей функциональности компилятора. Класс
    Compiler выступает в роли фасада: он предоставляет простой интерфейс к более сложной подсистеме.
    Он «склеивает» классы, реализующие функциональность компилятора, но не скрывает их полностью. Фасад компилятора упрощает работу большин- ства программистов, не скрывая низкоуровневую функциональность для тех немногих, кому она нужна.
    StackMachineCodeGenerator
    RISCCodeGenerator
    Stream
    Statement Node
    VariableNode
    ExpressionNode
    BytecodeStream
    CodeGenerator
    Scanner
    Parser
    Token
    Symbol
    ProgramNode
    ProgramNodeBuilder
    Compiler
    Compile()
    Классы
    подсистемы
    компиляции

    Паттерн Facade (фасад)
    223
    Применимость
    Основные условия для применения паттерна фасад:
    „
    „
    предоставление простого интерфейса к сложной подсистеме. Часто под- системы усложняются по мере развития. Применение большинства пат- тернов приводит к тому, что в системе появляется множество мелких классов. Такую подсистему проще повторно использовать и настраи- вать под конкретные нужды, но вместе с тем применять подсистему тем клиентам, которым не нужно ее настраивать, становится труднее. Фасад предлагает некоторый вид системы по умолчанию, устраивающий боль- шинство клиентов. И лишь те клиенты, которым нужны более широкие возможности настройки, могут напрямую обратиться к тому, что нахо- дится за фасадом;
    „
    „
    многочисленные зависимости между клиентами и классами реализации
    абстракции. Фасад позволяет отделить подсистему как от клиентов, так и от других подсистем, что, в свою очередь, способствует независимости подсистем и повышению уровня переносимости;
    „
    „
    требуется разложить подсистему на отдельные уровни. Используйте фасад для определения точки входа на каждый уровень подсистемы.
    Если подсистемы зависят друг от друга, то зависимости можно упро- стить, разрешив подсистемам обмениваться информацией только через фасады.
    Структура
    Фасад
    Классы подсистемы

    224
    Глава 4. Структурные паттерны
    Участники
    „
    „
    Facade (
    Compiler
    ) — фасад:
    • «знает», каким классам подсистемы адресовать запрос;
    • делегирует запросы клиентов подходящим объектам внутри подси- стемы;
    „
    „
    Классы подсистемы (
    Scanner
    ,
    Parser
    ,
    ProgramNode и т. д.):
    • реализуют функциональность подсистемы;
    • выполняют работу, порученную объектом Facade;
    • ничего не «знают» о существовании фасада, то есть не хранят ссылок на него.
    Отношения
    Клиенты общаются с подсистемой, посылая запросы фасаду. Он переадре- сует их подходящим объектам внутри подсистемы. Хотя основную работу выполняют именно объекты подсистемы, фасаду, возможно, придется пре- образовать свой интерфейс в интерфейсы подсистемы.
    Клиенты, пользующиеся фасадом, не имеют прямого доступа к объектам подсистемы.
    Результаты
    Основные достоинства паттерна фасад:
    „
    „
    он изолирует клиентов от компонентов подсистемы, уменьшая тем самым число объектов, с которыми клиентам приходится иметь дело, и упрощая работу с подсистемой;
    „
    „
    позволяет ослабить связанность между подсистемой и ее клиентами.
    Зачастую компоненты подсистемы сильно связаны. Слабая связанность позволяет видоизменять компоненты, не затрагивая при этом клиентов.
    Фасады помогают разложить систему на уровни и структурировать за- висимости между объектами, а также избежать сложных и циклических зависимостей. Это может оказаться важным, если клиент и подсистема реализуются независимо.
    Уменьшение числа зависимостей на стадии компиляции чрезвычайно важно в больших системах. Хочется, конечно, чтобы время, уходящее на перекомпиляцию после изменения классов подсистемы, было ми- нимальным. Сокращение числа зависимостей за счет фасадов может

    Паттерн Facade (фасад)
    225
    уменьшить количество нуждающихся в повторной компиляции файлов после небольшой модификации какой-нибудь важной подсистемы. Фасад может также упростить процесс переноса системы на другие платформы, поскольку уменьшается вероятность того, что в результате изменения одной подсистемы понадобится изменять и все остальные;
    „
    „
    фасад не препятствует приложениям напрямую обращаться к классам подсистемы, если это необходимо. Таким образом, у вас есть выбор меж- ду простотой и общностью.
    Реализация
    При реализации фасада следует обратить внимание на следующие вопросы:
    „
    „
    ослабление связанности клиента с подсистемой. Степень связанности можно значительно уменьшить, если сделать класс
    Facade абстрактным.
    Его конкретные подклассы будут соответствовать различным реализа- циям подсистемы. Тогда клиенты смогут взаимодействовать с подси- стемой через интерфейс абстрактного класса
    Facade
    . Абстрактное свя- зывание изолирует клиентов от информации о том, какая реализация подсистемы используется.
    Вместо порождения подклассов можно сконфигурировать объект
    Facade различными объектами подсистем. Для настройки фасада достаточно заменить один или несколько таких объектов;
    „
    „
    открытые и закрытые классы подсистем. Подсистема похожа на класс в том отношении, что у обоих есть интерфейсы и оба что-то инкапсули- руют. Класс инкапсулирует состояние и операции, а подсистема — клас- сы. И если полезно различать открытый и закрытый интерфейсы клас- са, то не менее разумно говорить об открытом и закрытом интерфейсах подсистемы.
    Открытый интерфейс подсистемы состоит из классов, к которым имеют доступ все клиенты; закрытый интерфейс доступен только для расшире- ния подсистемы. Класс
    Facade
    , конечно же, является частью открытого интерфейса, но это не единственная часть. Другие классы подсистемы также могут быть открытыми. Например, в подсистеме компиляции классы
    Parser и
    Scanner являются частью открытого интерфейса.
    Объявлять классы подсистемы закрытыми бывает полезно, но такая возможность поддерживается немногими объектно-ориентированными языками. И в C++, и в Smalltalk для классов традиционно использовалось глобальное пространство имен. Однако недавно комитет по стандарти-

    226
    Глава 4. Структурные паттерны зации C++ добавил к языку пространства имен [Str94], и это позволило предоставить доступ только к открытым классам подсистемы.
    Пример кода
    Рассмотрим более подробно, как создать фасад для подсистемы компиляции.
    В подсистеме компиляции определен класс
    BytecodeStream
    , который реали- зует поток объектов
    Bytecode
    . Объект
    Bytecode инкапсулирует байтовый код, с помощью которого описываются машинные команды. В этой же подсистеме определен еще класс
    Token для объектов, инкапсулирующих лексемы языка программирования.
    Класс
    Scanner принимает на входе поток символов и генерирует поток лек- сем, по одной каждый раз:
    class Scanner {
    public:
    Scanner(istream&);
    virtual

    Scanner();
    virtual Token& Scan();
    private:
    istream& _inputStream;
    };
    Класс
    Parser использует класс
    ProgramNodeBuilder для построения дерева разбора из лексем, возвращенных классом
    Scanner
    :
    class Parser { public:
    Parser(); virtual Parser(); virtual void Parse(Scanner&, ProgramNodeBuilder&);
    };
    Parser вызывает
    ProgramNodeBuilder для инкрементного построения дерева.
    Взаимодействие этих классов описывается паттерном строитель (124):
    class ProgramNodeBuilder {
    public:
    ProgramNodeBuilder();
    virtual ProgramNode* NewVariable(
    const char* variableName
    ) const;

    Паттерн Facade (фасад)
    227
    virtual ProgramNode* NewAssignment(
    ProgramNode* variable, ProgramNode* expression
    ) const;
    virtual ProgramNode* NewReturnStatement(
    ProgramNode* value
    ) const;
    virtual ProgramNode* NewCondition(
    ProgramNode* condition,
    ProgramNode* truePart, ProgramNode* falsePart
    ) const;
    // ...
    ProgramNode* GetRootNode();
    private:
    ProgramNode* _node;
    };
    Дерево разбора состоит из экземпляров подклассов класса
    ProgramNode
    , таких как
    StatementNode
    ,
    ExpressionNode и т. д. Иерархия классов
    ProgramNode
    — это пример паттерна компоновщик (196). Класс
    ProgramNode определяет интерфейс для выполнения операций с узлом программы и его потомками, если таковые имеются:
    class ProgramNode {
    public:
    // операции с узлом программы virtual void GetSourcePosition(int& line, int& index);
    // ...
    // операции с потомками virtual void Add(ProgramNode*);
    virtual void Remove(ProgramNode*);
    // ...
    virtual void Traverse(CodeGenerator&);
    protected:
    ProgramNode();
    };
    Операция
    Traverse
    (обход) принимает объект
    CodeGenerator
    (кодогенера- тор) в качестве параметра. Подклассы
    ProgramNode используют этот объект для генерирования машинного кода в форме объектов
    Bytecode
    , которые помещаются в поток
    BytecodeStream
    . Класс
    CodeGenerator описывается паттерном посетитель (379):

    228
    Глава 4. Структурные паттерны class CodeGenerator {
    public:
    virtual void Visit(StatementNode*);
    virtual void Visit(ExpressionNode*);
    // ...
    protected:
    CodeGenerator(BytecodeStream&);
    protected:
    BytecodeStream& _output;
    };
    У
    CodeGenerator есть подклассы, генерирующие машинный код для раз- личных аппаратных архитектур — например
    StackMachineCodeGenerator и
    RISCCodeGenerator
    Каждый подкласс
    ProgramNode реализует операцию
    Traverse и обращается к ней для обхода своих потомков. В свою очередь, каждый потомок ре- курсивно делает то же самое для своих потомков. Например, в подклассе
    ExpressionNode
    (узел выражения) операция
    Traverse определена так:
    void ExpressionNode::Traverse (CodeGenerator& cg) {
    cg.Visit(this);
    ListIterator i(_children);
    for (i.First(); !i.IsDone(); i.Next()) {
    i.CurrentItem()->Traverse(cg);
    }
    }
    Классы, о которых мы говорили до сих пор, составляют подсистему компиля- ции. А теперь введем класс
    Compiler
    , который будет служить фасадом, позво- ляющим собрать все эти фрагменты воедино. Класс
    Compiler предоставляет простой интерфейс для компилирования исходного текста и генерирования кода для конкретной машины:
    class Compiler {
    public:
    Compiler();
    virtual void Compile(istream&, BytecodeStream&);
    };
    void Compiler::Compile (
    istream& input, BytecodeStream& output
    ) {
    Scanner scanner(input);

    Паттерн Facade (фасад)
    1   ...   15   16   17   18   19   20   21   22   ...   38


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