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

  • Product

  • ConcreteCreator

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


    Скачать 6.37 Mb.
    НазваниеЭ. Гамма, Р. Хелм
    АнкорFactorial
    Дата14.03.2022
    Размер6.37 Mb.
    Формат файлаpdf
    Имя файлаPatterny_Obektno-Orientirovannogo_Proektirovania_2020.pdf
    ТипДокументы
    #395452
    страница11 из 38
    1   ...   7   8   9   10   11   12   13   14   ...   38
    128
    Глава 3. Порождающие паттерны ние и внутреннюю структуру продукта, а также процесс его сборки. По- скольку продукт конструируется через абстрактный интерфейс, то для изменения внутреннего представления достаточно всего лишь опреде- лить новый вид строителя;
    „
    „
    изолирует код, реализующий конструирование и представление. Паттерн строитель улучшает модульность, инкапсулируя способ конструирова- ния и представления сложного объекта. Клиентам ничего не надо знать о классах, определяющих внутреннюю структуру продукта; эти классы не входят в интерфейс строителя.
    Каждый конкретный строитель
    ConcreteBuilder содержит весь код, необ- ходимый для создания и сборки конкретного вида продукта. Код пишется только один раз, после чего разные распорядители могут использовать его повторно для построения вариантов продукта из одних и тех же частей.
    В примере с RTF-документом мы могли бы определить загрузчик для формата, отличного от RTF (скажем,
    SGMLReader
    ), и воспользоваться теми же классами
    TextConverter для генерирования представлений SGML- документов в виде ASCII-текста, TeX-текста или текстового виджета;
    „
    „
    предоставляет более точный контроль над процессом конструирования.
    В отличие от порождающих паттернов, которые сразу конструируют весь объект целиком, строитель делает это шаг за шагом под управле- нием распорядителя. И лишь когда продукт завершен, распорядитель за- бирает его у строителя. Поэтому интерфейс строителя в большей степени отражает процесс конструирования продукта, нежели другие порожда- ющие паттерны. Это позволяет обеспечить более тонкий контроль над процессом конструирования, а значит, и над внутренней структурой го- тового продукта.
    Реализация
    Обычно существует абстрактный класс
    Builder
    , в котором определены операции для каждого компонента, который может потребовать создать распорядитель. По умолчанию эти операции ничего не делают. Но в классе конкретного строителя
    ConcreteBuilder они замещены для тех компонентов, в создании которых он принимает участие.
    Также существуют и другие аспекты реализации, заслуживающие внимания:
    „
    „
    интерфейс сборки и конструирования. Строители конструируют свои продукты шаг за шагом, поэтому интерфейс класса
    Builder должен быть достаточно общим, чтобы обеспечить конструирование при любом виде конкретного строителя.

    Паттерн Builder (строитель)
    129
    Ключевой аспект проектирования связан с выбором модели процесса конструирования и сборки. Обычно бывает достаточно модели, в которой результаты выполнения запросов на конструирование просто присоеди- няются к продукту. В примере с RTF-документами строитель преобразует и добавляет очередную лексему к уже конвертированному тексту.
    Но иногда может потребоваться доступ к отдельным частям сконстру- ированного к данному моменту продукта. В примере с лабиринтом, который будет описан в разделе «Пример кода», интерфейс класса
    MazeBuilder позволяет добавлять дверь между уже существующими комнатами. Другим примером являются древовидные структуры — ска- жем, деревья синтаксического разбора, которые строятся снизу вверх.
    В этом случае строитель возвращает узлы-потомки распорядителю, который затем передает их обратно строителю, чтобы тот мог построить родительские узлы;
    „
    „
    почему нет абстрактного класса для продуктов? В типичном случае продукты, изготавливаемые различными строителями, имеют настолько разные представления, что изобретение для них общего родительского класса ничего не дает. В примере с RTF-документами трудно предста- вить себе общий интерфейс у объектов
    ASCIIText и
    TextWidget
    , да он и не нужен. Поскольку клиент обычно конфигурирует распорядителя подходящим конкретным строителем, то, надо полагать, ему известно, какой именно подкласс класса
    Builder используется и как нужно об- ращаться с произведенными продуктами;
    „
    „
    пустые методы класса Builder по умолчанию. В C++ методы строителя намеренно не объявлены чисто виртуальными функциями. Вместо это- го они определены как пустые функции, что позволяет подклассу заме- щать только те операции, которые представляют для него интерес.
    Пример кода
    Определим вариант функции
    CreateMaze
    , которая получает в аргументе строителя, принадлежащего классу
    MazeBuilder
    Класс
    MazeBuilder определяет следующий интерфейс для построения ла- биринтов:
    class MazeBuilder { public: virtual void BuildMaze() { } virtual void BuildRoom(int room) { } virtual void BuildDoor(int roomFrom, int roomTo) { }

    130
    Глава 3. Порождающие паттерны virtual Maze* GetMaze() { return 0; } protected:
    MazeBuilder();
    };
    Этот интерфейс позволяет создавать три сущности: лабиринт, комнату с кон- кретным номером, двери между пронумерованными комнатами. Операция
    GetMaze возвращает лабиринт клиенту. В подклассах
    MazeBuilder данная операция переопределяется для возвращения реально созданного лабиринта.
    Все операции построения лабиринта в классе
    MazeBuilder по умолчанию ничего не делают. Но они не объявлены чисто виртуальными, чтобы в про- изводных классах можно было замещать лишь часть методов.
    Имея интерфейс
    MazeBuilder
    , можно изменить функцию
    CreateMaze
    , чтобы она получала строителя в параметре:
    Maze* MazeGame::CreateMaze (MazeBuilder& builder) { builder.BuildMaze(); builder.BuildRoom(1); builder.BuildRoom(2); builder.BuildDoor(1, 2); return builder.GetMaze();
    }
    Сравните эту версию
    CreateMaze с первоначальной. Обратите внимание, как строитель скрывает внутреннее представление лабиринта, то есть классы комнат, дверей и стен, и как эти части собираются вместе для завершения построения лабиринта. Кто-то, может, и догадается, что для представления комнат и дверей есть особые классы, но ничто не указывает на существование такого класса для стен. За счет этого становится проще модифицировать спо- соб представления лабиринта, поскольку ни один из клиентов
    MazeBuilder изменять не надо.
    Как и другие порождающие паттерны, строитель инкапсулирует способ соз- дания объектов — в данном случае с помощью интерфейса, определенного классом
    MazeBuilder
    . Это означает, что
    MazeBuilder может повторно ис- пользоваться для построения лабиринтов разных видов. В качестве примера возьмем функцию
    CreateComplexMaze
    :
    Maze* MazeGame::CreateComplexMaze (MazeBuilder& builder) { builder.BuildRoom(1);
    // ...

    Паттерн Builder (строитель)
    131
    builder.BuildRoom(1001); return builder.GetMaze();
    }
    Обратите внимание:
    MazeBuilder не создает лабиринты самостоятельно, его основная цель — просто определить интерфейс для создания лабиринтов.
    Пустые реализации в этом интерфейсе определены только для удобства.
    Реальную работу выполняют подклассы
    MazeBuilder
    Подкласс
    StandardMazeBuilder содержит реализацию построения простых лабиринтов. Создаваемый лабиринт хранится в переменной
    _currentMaze
    :
    class StandardMazeBuilder : public MazeBuilder { public:
    StandardMazeBuilder(); virtual void BuildMaze(); virtual void BuildRoom(int); virtual void BuildDoor(int, int); virtual Maze* GetMaze(); private:
    Direction CommonWall(Room*, Room*);
    Maze* _currentMaze;
    };
    CommonWall
    (общая стена) — это вспомогательная операция, которая опре- деляет направление общей стены между двумя комнатами.
    Конструктор
    StandardMazeBuilder просто инициализирует
    _currentMaze
    :
    StandardMazeBuilder::StandardMazeBuilder () {
    _currentMaze = 0;
    }
    BuildMaze создает экземпляр класса
    Maze
    , который будет собираться другими операциями и в итоге будет возвращен клиенту (с помощью
    GetMaze
    ):
    void StandardMazeBuilder::BuildMaze () {
    _currentMaze = new Maze;
    }
    Maze* StandardMazeBuilder::GetMaze () {
    return _currentMaze;
    }

    132
    Глава 3. Порождающие паттерны
    Операция
    BuildRoom создает комнату и строит вокруг нее стены:
    void StandardMazeBuilder::BuildRoom (int n) { if (!_currentMaze->RoomNo(n)) {
    Room* room = new Room(n);
    _currentMaze->AddRoom(room); room->SetSide(North, new Wall); room->SetSide(South, new Wall); room->SetSide(East, new Wall); room->SetSide(West, new Wall);
    }
    }
    Чтобы построить дверь между двумя комнатами,
    StandardMazeBuilder на- ходит обе комнаты в лабиринте и их общую стену:
    void StandardMazeBuilder::BuildDoor (int n1, int n2) {
    Room* r1 = _currentMaze->RoomNo(n1);
    Room* r2 = _currentMaze->RoomNo(n2);
    Door* d = new Door(r1, r2);
    r1->SetSide(CommonWall(r1,r2), d);
    r2->SetSide(CommonWall(r2,r1), d);
    }
    Теперь для создания лабиринта клиенты могут использовать
    CreateMaze в сочетании с
    StandardMazeBuilder
    :
    Maze* maze;
    MazeGame game;
    StandardMazeBuilder builder; game.CreateMaze(builder); maze = builder.GetMaze();
    Мы могли бы поместить все операции класса
    StandardMazeBuilder в класс
    Maze и позволить каждому лабиринту строить самого себя. Но чем меньше класс
    Maze
    , тем проще в нем разобраться и внести изменения, а
    StandardMazeBuilder легко отделяется от
    Maze
    . Еще важнее то, что раз- деление этих двух классов позволяет создать множество разновидностей класса
    MazeBuilder
    , в каждом из которых есть собственные классы для комнат, дверей и стен.
    Необычной разновидностью
    MazeBuilder является класс
    CountingMazeBuilder
    Он вообще не создает никакого лабиринта, а лишь подсчитывает число ком- понентов разного вида, которые могли бы быть созданы:

    Паттерн Builder (строитель)
    133
    class CountingMazeBuilder : public MazeBuilder {
    public:
    CountingMazeBuilder();
    virtual void BuildMaze();
    virtual void BuildRoom(int);
    virtual void BuildDoor(int, int);
    virtual void AddWall(int, Direction);
    void GetCounts(int&, int&) const;
    private:
    int _doors;
    int _rooms;
    };
    Конструктор инициализирует счетчики, а замещенные операции класса
    MazeBuilder увеличивают их:
    CountingMazeBuilder::CountingMazeBuilder () {
    _rooms = _doors = 0;
    }
    void CountingMazeBuilder::BuildRoom (int) {
    _rooms++;
    }
    void CountingMazeBuilder::BuildDoor (int, int) {
    _doors++;
    }
    void CountingMazeBuilder::GetCounts (
    int& rooms, int& doors
    ) const {
    rooms = _rooms;
    doors = _doors;
    }
    А вот как клиент мог бы использовать класс
    CountingMazeBuilder
    :
    int rooms, doors;
    MazeGame game;
    CountingMazeBuilder builder;
    game.CreateMaze(builder);
    builder.GetCounts(rooms, doors);
    cout << "В лабиринте есть "
    << rooms << " комнат и "
    << doors << " дверей" << endl;

    134
    Глава 3. Порождающие паттерны
    Известные применения
    Приложение для конвертирования из формата RTF взято из библиотеки
    ET++ [WGM88]. Строитель используется в ней для обработки текста, хра- нящегося в формате RTF.
    Паттерн строитель широко применяется в языке Smalltalk80 [Par90]:
    „
    „
    класс
    Parser в подсистеме компиляции — это распорядитель, которому в качестве аргумента передается объект
    ProgramNodeBuilder
    . Объект класса
    Parser извещает объект
    ProgramNodeBuilder после распознава- ния каждой синтаксической конструкции. После завершения разбора
    Parser обращается к строителю за созданным деревом разбора и возвра- щает его клиенту;
    „
    „
    ClassBuilder
    — это строитель, которым пользуются все классы для соз- дания своих подклассов. В данном случае этот класс выступает одно- временно в качестве распорядителя и продукта;
    „
    „
    ByteCodeStream
    — это строитель, который создает откомпилированный метод в виде массива байт.
    ByteCodeStream является примером нестан- дартного применения паттерна строитель, поскольку сложный объект представляется в виде массива байт, а не обычного объекта Smalltalk.
    Но интерфейс к
    ByteCodeStream типичен для строителя, и этот класс лег- ко можно было бы заменить другим, который представляет программу в виде составного объекта.
    Каркас Service Configurator из Adaptive Communications Environment ис- пользует строителя для построения компонентов сетевых служб, связанных с сервером во время выполнения [SS94]. Описание компонентов формули- руется на языке конфигурации, который разбирается парсером LALR(1).
    Семантические действия парсера выполняют со строителем операции, добавляющие информацию в компонент службы. В данном случае парсер выполняет функции распорядителя.
    Родственные паттерны
    Абстрактная фабрика (113) похожа на строителя в том смысле, что может конструировать сложные объекты. Основное различие между ними в том, что строитель специализируется на пошаговом конструировании объекта, а абстрактная фабрика — на создании семейств объектов (простых или слож- ных). Строитель возвращает продукт на последнем шаге, тогда как с точки зрения абстрактной фабрики продукт возвращается немедленно.
    Паттерн компоновщик (196) — это то, что часто создает строитель.

    Паттерн Factory Method (фабричный метод)
    135
    ПАТТЕРН FACTORY METHOD (ФАБРИЧНЫЙ МЕТОД)
    Название и классификация паттерна
    Фабричный метод — паттерн, порождающий классы.
    Назначение
    Определяет интерфейс для создания объекта, но оставляет подклас- сам решение о том, экземпляры какого класса должны создаваться. Фаб- ричный метод позволяет классу делегировать создание экземпляров под- классам.
    Другие названия
    Virtual Constructor
    (виртуальный конструктор).
    Мотивация
    Каркасы пользуются абстрактными классами для определения и поддер- жания отношений между объектами. Кроме того, каркас часто отвечает за создание самих объектов.
    Рассмотрим каркас для приложений, способных представлять пользовате- лю сразу несколько документов. Две основных абстракции в таком карка- се — это классы
    Application и
    Document
    . Оба класса абстрактные, поэтому клиенты должны порождать от них подклассы для создания специфичных для приложения реализаций. Например, чтобы создать приложение для рисования, мы определим классы
    DrawingApplication и
    DrawingDocument
    Класс
    Application отвечает за управление документами и создает их по мере необходимости — допустим, когда пользователь выбирает из меню пункт
    Open
    (открыть) или
    New
    (создать).
    Поскольку выбор подкласса
    Document для создания экземпляра зависит от приложения, то
    Application не может «предсказать», что именно понадо- бится. Этот класс знает только то, когда нужно создать экземпляр нового документа, а не какой документ создать. Возникает дилемма: каркас должен создавать экземпляры класса, но «знает» он лишь об абстрактных классах, экземпляры которых создать нельзя.
    Проблема решается при помощи паттерна фабричный метод. Он инкапсули- рует информацию о том, какой подкласс класса
    Document должен создаваться, и выводит это знание за пределы каркаса.

    136
    Глава 3. Порождающие паттерны
    MyDocument
    Document
    Open()
    Close()
    Save()
    Revert()
    Application
    CreateDocument()
    NewDocument()
    OpenDocument()
    MyApplication
    CreateDocument()
    Document* doc = CreateDocument();
    docs.Add(doc);
    doc>Open(); return new MyDocument docs
    Подклассы класса
    Application переопределяют абстрактную операцию
    CreateDocument таким образом, чтобы она возвращала подходящий подкласс класса
    Document
    . Как только будет создан экземпляр подкласса
    Application
    , он может создавать экземпляры специфических для приложения докумен- тов, ничего не зная об их классах. Операция
    CreateDocument называется
    фабричным методом, так как она отвечает за «изготовление» объекта.
    Применимость
    Основные условия для применения паттерна фабричный метод:
    „
    „
    классу заранее неизвестно, объекты каких классов ему нужно создавать;
    „
    „
    класс спроектирован так, чтобы объекты, которые он создает, определя- лись подклассами;
    „
    „
    класс делегирует свои обязанности одному из нескольких вспомога- тельных подклассов, и вам нужно локализовать информацию о том, ка- кой класс принимает эти обязанности на себя.
    Структура
    Product
    ConcreteProduct
    Creator
    FactoryMethod()
    AnOperation()
    ConcreteCreator
    FactoryMethod()
    product = FactoryMethod()
    return new ConcreteProduct

    Паттерн Factory Method (фабричный метод)
    137
    Участники
    „
    „
    Product (
    Document
    ) — продукт:
    • определяет интерфейс объектов, создаваемых фабричным методом;
    „
    „
    ConcreteProduct (
    MyDocument
    ) — конкретный продукт:
    • реализует интерфейс
    Product
    ;
    „
    „
    Creator (
    Application
    ) — создатель:
    • объявляет фабричный метод, возвращающий объект типа Product.
    Creator может также определять реализацию по умолчанию фабрич- ного метода
    , который возвращает объект
    ConcreteProduct
    ;
    • может вызывать фабричный метод для создания объекта
    Product
    „
    „
    ConcreteCreator (
    MyApplication
    ) — конкретный создатель:
    • замещает фабричный метод, возвращающий объект
    СoncreteProduct
    Отношения
    Создатель полагается на свои подклассы в определении фабричного метода, который будет возвращать экземпляр подходящего конкретного продукта.
    Результаты
    Фабричные методы избавляют проектировщика от необходимости встраи- вать в код зависящие от приложения классы. Код имеет дело только с интер- фейсом класса
    Product
    , поэтому он может работать с любыми определенными пользователями классами конкретных продуктов.
    Потенциальный недостаток фабричного метода состоит в том, что клиентам, возможно, придется создавать подкласс класса
    Creator для создания лишь одного объекта
    ConcreteProduct
    . Порождение подклассов оправданно, если клиенту так или иначе приходится создавать подклассы
    Creator
    , в противном случае клиенту придется иметь дело с дополнительным уровнем подклассов.
    А вот еще два последствия применения паттерна фабричный метод:
    „
    „
    подклассам предоставляются операции-зацепки (hooks). Создание объ- ектов внутри класса с помощью фабричного метода всегда оказывается более гибким решением, чем непосредственное создание. Фабричный метод создает в подклассах операции-зацепки для предоставления рас- ширенной версии объекта.
    В примере с документом класс
    Document мог бы определить фабричный метод
    CreateFileDialog
    , который создает диалоговое окно по умолчанию

    1   ...   7   8   9   10   11   12   13   14   ...   38


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