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

  • Application

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


    Скачать 6.37 Mb.
    НазваниеЭ. Гамма, Р. Хелм
    АнкорFactorial
    Дата14.03.2022
    Размер6.37 Mb.
    Формат файлаpdf
    Имя файлаPatterny_Obektno-Orientirovannogo_Proektirovania_2020.pdf
    ТипДокументы
    #395452
    страница32 из 38
    1   ...   28   29   30   31   32   33   34   35   ...   38
    366
    Глава 5. Паттерны поведения жестко «зашивается» в класс
    Context
    . Реализации алгоритма и контекста смешиваются, что затрудняет понимание, сопровождение и расширение контекста. Кроме того, заменить алгоритм динамически уже не удастся.
    В результате вы получаете множество родственных классов, отличающих- ся только алгоритмом или поведением. Инкапсуляция алгоритма в от- дельный класс
    Strategy позволяет изменять его независимо от контекста;
    „
    „
    стратегии позволяют избавиться от условных конструкций. С паттер- ном стратегия удается отказаться от условных операторов при выборе нужного поведения. Когда различные поведения помещаются в один класс, трудно выбрать нужное без применения условных операторов.
    Инкапсуляция же каждого поведения в отдельный класс
    Strategy ре- шает эту проблему.
    Так, без использования стратегий код для разбиения текста на строки мог бы выглядеть следующим образом:
    void Composition::Repair () {
    switch (_breakingStrategy) {
    case SimpleStrategy:
    ComposeWithSimpleCompositor();
    break;
    case TeXStrategy:
    ComposeWithTeXCompositor();
    break;
    // ...
    }
    // При необходимости объединить результаты
    // с существующей композицией
    }
    Паттерн стратегия позволяет обойтись без конструкции выбора за счет делегирования задачи разбиения на строки объекту
    Strategy
    :
    void Composition::Repair () {
    _compositor->Compose();
    // При необходимости объединить результаты
    // с существующей композицией
    }
    Если код содержит много условных операторов, то часто это признак того, что нужно применить паттерн стратегия;
    „
    „
    выбор реализации. Стратегии могут предлагать различные реализации одного и того же поведения. Клиент вправе выбирать подходящую стра- тегию в зависимости от своих требований к быстродействию и памяти;

    Паттерн Strategy (стратегия)
    367
    „
    „
    клиенты должны знать о различных стратегиях. Потенциальный не- достаток этого паттерна в том, что для выбора подходящей стратегии клиент должен понимать, чем отличаются разные стратегии. Поэтому наверняка придется раскрыть клиенту некоторые особенности реализа- ции. Отсюда следует, что паттерн стратегия стоит применять лишь тогда, когда различия в поведении важны для клиента;
    „
    „
    затраты на передачу информации между стратегией и контекстом.
    Интерфейс
    Strategy совместно используется всеми подклассами
    ConcreteStrategy
    — какой бы сложной или тривиальной ни была их ре- ализация. Поэтому вполне вероятно, что некоторые стратегии не будут пользоваться всей передаваемой им информацией, особенно простые.
    Это означает, что в отдельных случаях контекст создаст и проинициа- лизирует параметры, которые никому не нужны. Если возникнет про- блема, то между классами
    Strategy и
    Context придется установить бо- лее тесную связь;
    „
    „
    увеличение числа объектов. Применение стратегий увеличивает число объектов в приложении. Иногда эти издержки можно сократить, если реализовать стратегии в виде объектов без состояния, которые могут со- вместно использоваться несколькими контекстами. Остаточное состо- яние хранится в самом контексте и передается при каждом обращении к объекту-стратегии. Совместно используемые стратегии не должны со- хранять состояние между вызовами. В описании паттерна приспособле- нец (231) этот подход обсуждается более подробно.
    Реализация
    Рассмотрим следующие вопросы реализации:
    „
    „
    определение интерфейсов классов Strategy и Context. Интерфейсы классов
    Strategy и
    Context должны предоставить объекту класса
    ConcreteStrategy эффективный доступ к любым данным контекста, и наоборот.
    Например,
    Context может передавать данные в параметрах операций класса
    Strategy
    . Тем самым разрывается тесная связь между контекстом и стратегией. С другой стороны, при этом контекст может передавать данные, которые стратегии не нужны.
    Другой способ — передача самого контекста в аргументе. В таком случае стратегия может явно запрашивать у него данные; также стратегия может хранить ссылку на свой контекст, так что передавать вообще ничего не придется. И в том, и в другом случаях стратегия может запрашивать толь-

    368
    Глава 5. Паттерны поведения ко ту информацию, которая реально необходима. Но тогда в контексте должен быть определен более развитый интерфейс к своим данным, что несколько усиливает связанность классов
    Strategy и
    Context
    Выбор подхода зависит от конкретного алгоритма и требований, которые он предъявляет к данным;
    „
    „
    стратегии как параметры шаблона. В C++ для настройки класса стра- тегией можно использовать шаблоны. Этот способ хорош, только если:
    (1) стратегия определяется на этапе компиляции, и (2) ее не нужно менять во время выполнения. Тогда настраиваемый класс (например,
    Context
    ) определяется в виде шаблона, для которого класс
    Strategy яв- ляется параметром:
    template
    class Context {
    void Operation() { theStrategy.DoAlgorithm(); }
    // ... private:
    AStrategy theStrategy;
    };
    Затем этот класс настраивается классом
    Strategy в момент создания экземпляра:
    class MyStrategy {
    public:
    void DoAlgorithm();
    };
    Context aContext;
    При использовании шаблонов отпадает необходимость в абстрактном классе для определения интерфейса
    Strategy
    . Кроме того, передача стратегии в параметре шаблона позволяет статически связать стратегию с контекстом, вследствие чего повышается эффективность программы;
    „
    „
    объекты-стратегии можно не задавать. Класс
    Context можно упро- стить, если для него нормально не иметь никакой стратегии. Прежде чем обращаться к объекту
    Strategy
    , объект
    Context проверяет наличие стра- тегии. Если да, то работа продолжается как обычно, в противном случае контекст реализует некое поведение по умолчанию. Преимущество та- кого подхода в том, что клиентам вообще не нужно иметь дело со стра- тегиями, если их устраивает поведение по умолчанию.

    Паттерн Strategy (стратегия)
    369
    Пример кода
    Мы приведем высокоуровневый код для примера из раздела «Мотивация», в основе которого лежат классы
    Composition и
    Compositor из библиотеки
    InterViews [LCI+92].
    В классе
    Composition есть коллекция экземпляров класса
    Component
    , пред- ставляющих текстовые и графические элементы документа. Компоновщик, то есть некоторый подкласс класса
    Compositor
    , составляет из объектов-ком- понентов строки, реализуя ту или иную стратегию разбиения на строки.
    С каждым объектом ассоциирован его естественный размер, а также свойства растягиваемости и сжимаемости. Растягиваемость определяет, насколько возможно увеличивать объект по сравнению с его естественным размером, а сжимаемость — насколько возможно этот размер уменьшать. Композиция передает эти значения компоновщику, который использует их, чтобы найти оптимальное место для разбиения строки.
    class Composition {
    public:
    Composition(Compositor*);
    void Repair();
    private:
    Compositor* _compositor;
    Component* _components; // Список компонентов int _componentCount; // Количество компонентов int _lineWidth; // Ширина строки в композиции int* _lineBreaks; // Позиции точек разбиения строки
    // (измеренные в компонентах)
    int _lineCount; // Количество строк
    };
    Когда возникает необходимость изменить расположение элементов, компо- зиция запрашивает у компоновщика позиции точек разбиения строк. При этом она передает компоновщику три массива, в которых содержатся есте- ственные размеры, величины растягиваемости и сжимаемости компонентов.
    Кроме того, передается число компонентов, ширина строки и массив, в кото- рый компоновщик должен поместить позиции точек разрыва. Компоновщик возвращает число рассчитанных им точек разбиения.
    Интерфейс класса
    Compositor позволяет композиции передать компоновщи- ку всю необходимую ему информацию. Пример передачи данных стратегии:
    class Compositor {
    public:
    virtual int Compose(
    Coord natural[], Coord stretch[], Coord shrink[],

    370
    Глава 5. Паттерны поведения int componentCount, int lineWidth, int breaks[]
    ) = 0;
    protected:
    Compositor();
    };
    Заметим, что
    Compositor
    — это абстрактный класс. В его конкретных под- классах определяются различные стратегии разбиения на строки.
    Композиция вызывает своего компоновщика из операции
    Repair
    , которая прежде всего инициализирует массивы, содержащие естественные разме- ры, растягиваемость и сжимаемость каждого компонента (подробности мы опускаем). Затем
    Repair вызывает компоновщика для получения позиций точек разбиения и, наконец, отображает документ (этот код также опущен):
    void Composition::Repair () {
    Coord* natural;
    Coord* stretchability;
    Coord* shrinkability;
    int componentCount;
    int* breaks;
    // Подготовить массивы с желательными размерами компонентов
    // ...
    // Определить, где должны находиться точки разбиения:
    int breakCount;
    breakCount = _compositor->Compose(
    natural, stretchability, shrinkability,
    componentCount, _lineWidth, breaks
    );
    // Разместить компоненты с учетом точек разбиения
    // ...
    }
    Обратимся к подклассам класса
    Compositor
    . Класс
    SimpleCompositor для определения позиций точек разрыва анализирует компоненты по одному:
    class SimpleCompositor : public Compositor {
    public:
    SimpleCompositor();
    virtual int Compose(
    Coord natural[], Coord stretch[], Coord shrink[],
    int componentCount, int lineWidth, int breaks[]
    );
    // ...
    };

    Паттерн Strategy (стратегия)
    371
    Класс
    TeXCompositor использует более глобальную стратегию. Он рассма- тривает абзац целиком, принимая во внимание размеры и растягиваемость компонентов. Данный класс также пытается минимизировать ширину про- пусков между компонентами:
    class TeXCompositor : public Compositor {
    public:
    TeXCompositor();
    virtual int Compose(
    Coord natural[], Coord stretch[], Coord shrink[],
    int componentCount, int lineWidth, int breaks[]
    );
    // ...
    };
    Класс
    ArrayCompositor разбивает компоненты на строки, оставляя между ними равные промежутки:
    class ArrayCompositor : public Compositor {
    public:
    ArrayCompositor(int interval);
    virtual int Compose(
    Coord natural[], Coord stretch[], Coord shrink[],
    int componentCount, int lineWidth, int breaks[]
    );
    // ...
    };
    Не все из этих классов используют в полном объеме информацию, передан- ную
    Compose
    SimpleCompositor игнорирует растягиваемость компонентов, принимая во внимание только их естественную ширину.
    TeXCompositor ис- пользует всю переданную информацию, а
    ArrayCompositor игнорирует ее.
    При создании экземпляра класса
    Composition ему передается компоновщик, которым собираетесь пользоваться:
    Composition* quick = new Composition(new SimpleCompositor);
    Composition* slick = new Composition(new TeXCompositor);
    Composition* iconic = new Composition(new ArrayCompositor(100));
    Интерфейс класса
    Compositor тщательно спроектирован для поддержки всех алгоритмов размещения, которые могут быть реализованы в подклассах.
    Вряд ли вам захочется изменять данный интерфейс при появлении каждого нового подкласса, поскольку это означало бы переписывание уже существу- ющих подклассов. В общем случае именно интерфейсы классов
    Strategy

    372
    Глава 5. Паттерны поведения и
    Context определяют, насколько хорошо паттерн стратегия соответствует своему назначению.
    Известные применения
    Библиотеки ET++ [WGM88] и InterViews используют стратегии для инкап- суляции алгоритмов разбиения на строки — так, как мы только что видели.
    В системе RTL для оптимизации кода компиляторов [JML92] с помо- щью стратегий определяются различные схемы распределения регистров
    (
    RegisterAllocator
    ) и политики управления потоком команд (
    RISCscheduler
    ,
    CISCscheduler
    ). Это позволяет гибко настраивать оптимизатор для разных целевых машинных архитектур.
    Каркас ET++
    SwapsManager предназначен для построения программ, рас- считывающих цены для различных финансовых инструментов [EG92].
    Ключевыми абстракциями для него являются
    Instrument
    (инструмент) и
    YieldCurve
    (кривая дохода). Различные инструменты реализованы как подклассы класса
    Instrument
    YieldCurve рассчитывает коэффициенты дис- контирования, на основе которых вычисляется текущее значение будущего движения ликвидности. Оба класса делегируют часть своего поведения объектам-стратегиям класса
    Strategy
    . В каркасе присутствует семейство конкретных стратегий для генерирования движения ликвидности, оценки оборотов и вычисления коэффициентов дисконтирования. Можно создавать новые механизмы расчетов, конфигурируя классы
    Instrument и
    YieldCurve другими объектами конкретных стратегий. Этот подход поддерживает как использование существующих реализаций стратегий в различных сочета- ниях, так и определение новых.
    В библиотеке компонентов Гради Буча [BV90] стратегии используются как аргументы шаблонов. В классах коллекций поддерживаются три раз- новидности стратегий распределения памяти: управляемая (распределение из пула), контролируемая (распределение и освобождение защищаются блокировками) и неуправляемая (стандартное распределение памяти). Стра- тегия передается классу коллекции в аргументе шаблона в момент создания экземпляра. Например, для коллекции
    UnboundedCollection
    , в которой ис- пользуется неуправляемая стратегия, экземпляр создается конструкцией
    Un boundedCollection
    RApp — система для проектирования топологии интегральных схем [GA89,
    AG90]. Задача RApp — проложить контакты между различными подсисте- мами на схеме. Алгоритмы трассировки в RApp определены как подклассы абстрактного класса
    Router
    , который является стратегией.

    Паттерн Template Method (шаблонный метод)
    373
    В библиотеке ObjectWindows фирмы Borland [Bor94] стратегии использу- ются в диалоговых окнах для проверки правильности введенных пользова- телем данных. Например, можно контролировать, что число принадлежит заданному диапазону, а в данном поле должны быть только цифры. Не ис- ключено, что проверка корректности введенной строки потребует поиска данных по справочной таблице.
    Для инкапсуляции стратегий проверки в ObjectWindows используются объ- екты класса
    Validator
    — частный случай паттерна стратегия. Поля для ввода данных делегируют стратегию контроля необязательному объекту
    Validator
    Клиент при необходимости присоединяет таких проверяющих к полю (при- мер необязательной стратегии). В момент закрытия диалогового окна поля
    «просят» своих контролеров проверить правильность данных. В библиотеке имеются классы контролеров для наиболее распространенных случаев, на- пример
    RangeValidator для проверки принадлежности числа диапазону. Но клиент может легко определить и собственные стратегии проверки, порождая подклассы от класса
    Validator
    Родственные паттерны
    Приспособленец (231): объекты-стратегии в большинстве случаев подходят для применения паттерна приспособленец.
    ПАТТЕРН TEMPLATE METHOD (ШАБЛОННЫЙ МЕТОД)
    Название и классификация паттерна
    Шаблонный метод — паттерн поведения классов.
    Назначение
    Шаблонный метод определяет основу алгоритма и позволяет подклассам пере- определить некоторые шаги алгоритма, не изменяя его структуру в целом.
    Мотивация
    Рассмотрим каркас приложения, в котором имеются классы
    Application и
    Document
    . Класс
    Application отвечает за открытие существующих докумен- тов, хранящихся во внешнем формате (например, в файле). Объект класса
    Document представляет информацию документа после его прочтения из файла.
    Приложения, построенные на базе этого каркаса, могут порождать подклассы от классов
    Application и
    Document
    , отвечающие конкретным потребностям.

    374
    Глава 5. Паттерны поведения
    Например, графический редактор определит подклассы
    DrawApplication и
    DrawDocument
    , а электронная таблица — подклассы
    SpreadsheetApplication и
    SpreadsheetDocument
    AddDocument()
    OpenDocument()
    DoCreateDocument()
    CanOpenDocument()
    AboutToOpenDocument()
    Application
    DoCreateDocument()
    CanOpenDocument()
    AboutToOpenDocument()
    MyApplication
    Document
    MyDocument
    DoRead()
    Save()
    Open()
    Close()
    DoRead()
    docs return new MyDocument
    В абстрактном классе
    Application определен алгоритм открытия и чтения документа в операции
    OpenDocument
    :
    void Application::OpenDocument (const char* name) {
    if (!CanOpenDocument(name)) {
    // Обработать документ невозможно return;
    }
    Document* doc = DoCreateDocument();
    if (doc) {
    _docs->AddDocument(doc);
    AboutToOpenDocument(doc);
    doc->Open();
    doc->DoRead();
    }
    }
    Операция
    OpenDocument определяет все шаги открытия документа. Она проверяет, возможно ли открыть документ, создает объект класса
    Document
    , добавляет его к набору документов и читает документ из файла.
    Операцию вида
    OpenDocument мы будем называть шаблонным методом, описывающим алгоритм в категориях абстрактных операций, которые за- мещены в подклассах для получения нужного поведения. Подклассы класса

    Паттерн Template Method (шаблонный метод)
    375
    Application проверяют возможность открытия (
    CanOpenDocument
    ) и созда- ния документа (
    DoCreateDocument
    ). Подклассы класса
    Document считывают документ (
    DoRead
    ). Шаблонный метод определяет также операцию, которая позволяет подклассам
    Application получить информацию о том, что доку- мент вот-вот будет открыт (
    AboutToOpenDocument
    ).
    Определяя некоторые шаги алгоритма с помощью абстрактных операций, шаблонный метод фиксирует их последовательность, но позволяет реали- зовать их в подклассах классов
    Application и
    Document
    Применимость
    Основные условия для применения паттерна шаблонный метод:
    „
    „
    однократное использование инвариантных частей алгоритма, при этом ре- ализация изменяющегося поведения остается на усмотрение подклассов;
    „
    „
    необходимость вычленить и локализовать в одном классе поведение, общее для всех подклассов, чтобы избежать дублирования кода. Это хороший пример техники «вынесения за скобки с целью обобщения», описанной в работе Уильяма Опдайка (William Opdyke) и Ральфа
    Джонсона (Ralph Johnson) [OJ93]. Сначала выявляются различия в су- ществующем коде, которые затем выносятся в отдельные операции.
    В конечном итоге различающиеся фрагменты кода заменяются шаблон- ным методом, из которого вызываются новые операции;
    „
    „
    управление расширениями подклассов. Шаблонный метод можно опре- делить так, что он будет вызывать операции-зацепки (hooks) — см. раз- дел «Результаты» — в определенных точках, разрешив тем самым рас- ширение только в этих точках.
    Структура
    TemplateMethod()
    PrimitiveOperation1()
    PrimitiveOperation2()
    1   ...   28   29   30   31   32   33   34   35   ...   38


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