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

  • Figure

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


    Скачать 6.37 Mb.
    НазваниеЭ. Гамма, Р. Хелм
    АнкорFactorial
    Дата14.03.2022
    Размер6.37 Mb.
    Формат файлаpdf
    Имя файлаPatterny_Obektno-Orientirovannogo_Proektirovania_2020.pdf
    ТипДокументы
    #395452
    страница12 из 38
    1   ...   8   9   10   11   12   13   14   15   ...   38
    138
    Глава 3. Порождающие паттерны для открытия существующего документа. Подкласс этого класса мог бы определить специализированное для приложения диалоговое окно, заместив этот фабричный метод. В данном случае фабричный метод не является абстрактным, а содержит разумную реализацию по умолчанию;
    „
    „
    соединение параллельных иерархий. В примерах, которые мы рассматри- вали до сих пор, фабричные методы вызывались только создателем. Но это совершенно необязательно: клиенты тоже могут применять фабрич- ные методы, особенно при наличии параллельных иерархий классов.
    Параллельные иерархии возникают в случае, когда класс делегирует часть своих обязанностей другому классу, который не является производным от него. Рассмотрим, например, графические фигуры, которыми можно манипулировать интерактивно: растягивать, двигать или вращать с по- мощью мыши. Реализовать такие взаимодействия бывает непросто; часто приходится сохранять и обновлять информацию о текущем состоянии манипуляций. Но это состояние используется только во время самой манипуляции, поэтому помещать его в объект, представляющий фигуру, не следует. К тому же фигуры по-разному ведут себя, когда пользователь манипулирует ими. Например, растягивание отрезка может сводиться к изменению положения концевой точки, а растягивание текста — к из- менению междустрочных интервалов.
    При таких ограничениях лучше использовать отдельный объект-манипу- лятор
    Manipulator
    , который реализует взаимодействие и контролирует его текущее состояние. Разным фигурам будут соответствовать разные манипуляторы, являющиеся подклассом
    Manipulator
    . Получающаяся иерархия класса
    Manipulator параллельна (по крайней мере частично) иерархии класса
    Figure
    Figure
    CreateManipulator()
    ...
    TextFigure
    CreateManipulator()
    LineFigure
    CreateManipulator()
    Client
    Manipulator
    DownClick()
    Drag()
    UpClick()
    TextManipulator
    DownClick()
    Drag()
    UpClick()
    LineManipulator
    DownClick()
    Drag()
    UpClick()

    Паттерн Factory Method (фабричный метод)
    139
    Класс
    Figure предоставляет фабричный метод
    CreateManipulator
    , который позволяет клиентам создавать соответствующий фигуре манипулятор. Под- классы
    Figure замещают этот метод так, чтобы он возвращал подходящий для них подкласс
    Manipulator
    . Вместо этого класс
    Figure может реализовать
    CreateManipulator так, что он будет возвращать экземпляр класса
    Manipulator по умолчанию, а подклассы
    Figure могут наследовать это умолчание. Те классы фигур, которые работают по описанному принципу, не нуждаются в специальном манипуляторе, поэтому иерархии параллельны только отчасти.
    Обратите внимание, как фабричный метод определяет связь между обеими иерархиями классов. В нем локализуется знание о том, какие классы спо- собны работать совместно.
    Реализация
    Рассмотрим следующие вопросы, возникающие при использовании паттерна фабричный метод:
    „
    „
    две основных разновидности паттерна: (1) случай, когда класс
    Creator является абстрактным и не содержит реализации объявленного в нем фабричного метода, и (2)
    Creator
    — конкретный класс, в котором по умолчанию есть реализация фабричного метода. Редко, но встречается и абстрактный класс, имеющий реализацию по умолчанию.
    В первом случае для определения реализации необходимы подклассы, поскольку никакой разумной реализации по умолчанию не существует.
    При этом обходится проблема, связанная с необходимостью создания экземпляров заранее неизвестных классов. Во втором случае конкрет- ный класс
    Creator использует фабричный метод, главным образом ради повышения гибкости. Происходящее соответствует правилу: «Создавай объекты в отдельной операции, чтобы подклассы могли подменить способ их создания». Соблюдение этого правила гарантирует, что авторы под- классов смогут при необходимости изменить класс объектов, экземпляры которых создаются их родителем;
    „
    „
    параметризованные фабричные методы. Это еще один вариант пат- терна, который позволяет фабричному методу создавать разные виды продуктов. Фабричному методу передается параметр, который опреде- ляет вид создаваемого объекта. Все объекты, получающиеся с помощью фабричного метода, разделяют общий интерфейс
    Product
    . В примере с документами класс
    Application может поддерживать разные виды до- кументов. Вы передаете методу
    CreateDocument лишний параметр, кото- рый и определяет, документ какого вида нужно создать.

    140
    Глава 3. Порождающие паттерны
    В каркасе Unidraw для создания графических редакторов [VL90] исполь- зуется именно этот подход для реконструкции объектов, сохраненных на диске. Unidraw определяет класс
    Creator с фабричным методом
    Create
    , которому в аргументе передается идентификатор класса, определяющий, экземпляр какого класса должен создаваться. Когда Unidraw сохраняет объект на диске, он сначала записывает идентификатор класса, а затем его переменные экземпляра. При реконструкции объекта сначала читается идентификатор класса.
    После чтения идентификатора класса каркас вызывает операцию
    Create
    , передавая ей этот идентификатор как параметр.
    Create ищет конструктор соответствующего класса и с его помощью создает экземпляр. И наконец,
    Create вызывает операцию
    Read созданного объекта, которая считывает с диска остальную информацию и инициализирует переменные экзем- пляра.
    Параметризованный фабричный метод в общем случае имеет следующий вид (здесь
    MyProduct и
    YourProduct
    — подклассы
    Product
    ):
    class Creator {
    public:
    virtual Product* Create(ProductId);
    };
    Product* Creator::Create (ProductId id) {
    if (id == MINE) return new MyProduct;
    if (id == YOURS) return new YourProduct;
    // Повторить для всех остальных продуктов...
    return 0;
    }
    Замещение параметризованного фабричного метода позволяет легко и избирательно расширить или заменить продукты, которые изготавли- вает создатель. Можно завести новые идентификаторы для новых видов продуктов или ассоциировать существующие идентификаторы с другими продуктами.
    Например, подкласс
    MyCreator мог бы переставить местами
    MyProduct и
    YourProduct для поддержки третьего подкласса
    TheirProduct
    :
    Product* MyCreator::Create (ProductId id) { if (id == YOURS) return new MyProduct; if (id == MINE) return new YourProduct;
    // N.B.: YOURS и MINE переставлены

    Паттерн Factory Method (фабричный метод)
    141
    if (id == THEIRS) return new TheirProduct; return Creator::Create(id); // Вызывается, если других
    // вариантов не осталось
    }
    Обратите внимание, что в самом конце операция вызывает метод
    Create родительского класса. Это происходит из-за того, что
    MyCreator::Create обрабатывает только продукты
    YOURS
    ,
    MINE
    и
    THEIRS
    иначе, чем родитель- ский класс. Другие классы его не интересуют. По этой причине
    MyCreator
    расширяет некоторые виды создаваемых продуктов, а создание остальных поручает своему родительскому классу;
    „
    „
    вариации и проблемы, зависящие от конкретного языка. В разных язы- ках возникают собственные интересные варианты и некоторые ню- ансы.
    Так, в программах на Smalltalk часто используется метод, который воз- вращает класс создаваемого экземпляра. Фабричный метод
    Creator может воспользоваться возвращенным значением для создания продук- та, а
    ConcreteCreator может сохранить или даже вычислить это значе- ние. В результате привязка к типу конкретного создаваемого продукта
    ConcreteProduct происходит еще позже.
    В версии примера
    Document на языке Smalltalk метод documentClass может определяться в классе
    Application
    . Этот метод возвращает подходящий класс
    Document для создания экземпляров документов. Реализация ме- тода documentClass в классе
    MyApplication возвращает класс
    MyDocument
    Таким образом, в классе
    Application мы имеем clientMethod document := self documentClass new. documentClass self subclassResponsibility
    А в классе
    MyApplication
    :
    documentClass
    ^ MyDocument с возвращением класса
    MyDocument
    , экземпляр которого должен быть создан, приложению Application.
    Еще более гибкий подход, сходный с параметризованными фабричными методами, основан на сохранении создаваемого класса в переменной

    142
    Глава 3. Порождающие паттерны класса
    Application
    . В таком случае для изменения продукта не нужно будет порождать подкласс
    Application
    В C++ фабричные методы всегда являются виртуальными функция- ми, а часто даже чисто виртуальными. Нужно быть осторожней и не вызывать фабричные методы в конструкторе класса
    Creator
    : в этот момент фабричный метод в производном классе
    ConcreteCreator еще недоступен.
    Проблему можно обойти, если обращаться к продуктам только с помощью функций доступа, создающих продукт по запросу. Вместо того чтобы создавать конкретный продукт, конструктор просто инициализирует его нулем. Функция доступа возвращает продукт, но сначала проверяет, что он существует (и если не существует — создает продукт). Подобный под- ход часто называют отложенной инициализацией. В следующем примере показана типичная реализация:
    class Creator {
    public:
    Product* GetProduct();
    protected:
    virtual Product* CreateProduct();
    private:
    Product* _product;
    };
    Product* Creator::GetProduct () {
    if (_product == 0) {
    _product = CreateProduct();
    }
    return _product;
    }
    „
    „
    использование шаблонов, чтобы не порождать подклассы. К сожалению, возможна ситуация, когда вам придется порождать подклассы только для того, чтобы создать подходящие объекты-продукты. В C++ этого можно избежать, предоставив шаблонный подкласс класса
    Creator
    , па- раметризованный классом
    Product
    :
    class Creator {
    public:
    virtual Product* CreateProduct() = 0;
    };
    template
    class StandardCreator: public Creator {

    Паттерн Factory Method (фабричный метод)
    143
    public:
    virtual Product* CreateProduct();
    };
    template
    Product* StandardCreator::CreateProduct () {
    return new TheProduct;
    }
    С таким шаблоном клиент передает только класс продукта, порождать подклассы от
    Creator не требуется:
    class MyProduct : public Product {
    public:
    MyProduct();
    // ...
    };
    StandardCreator myCreator;
    „
    „
    соглашения об именах. Рекомендуется применять такие соглашения об именах, которые дают ясно понять, что вы пользуетесь фабричными методами. Например, каркас MacApp на платформе Macintosh [App89] всегда объявляет абстрактную операцию, которая определяет фабрич- ный метод, в виде
    Class* DoMakeClass()
    , где
    Class
    — это класс продукта.
    Пример кода
    Функция
    CreateMaze строит и возвращает лабиринт. Одна из проблем, связанных с этой функцией, состоит в том, что классы лабиринта, комнат, дверей и стен жестко «зашиты» в данной функции. Мы введем фабричные методы, которые позволят выбирать эти компоненты подклассам.
    Сначала определим фабричные методы в игре
    MazeGame для создания объ- ектов лабиринта, комнат, дверей и стен:
    class MazeGame {
    public:
    Maze* CreateMaze();
    // фабричные методы:
    virtual Maze* MakeMaze() const
    { return new Maze; }
    virtual Room* MakeRoom(int n) const
    { return new Room(n); }

    144
    Глава 3. Порождающие паттерны virtual Wall* MakeWall() const
    { return new Wall; }
    virtual Door* MakeDoor(Room* r1, Room* r2) const
    { return new Door(r1, r2); }
    };
    Каждый фабричный метод возвращает один из компонентов лабиринта.
    Класс
    MazeGame предоставляет реализации по умолчанию, которые возвра- щают простейшие варианты лабиринта, комнаты, двери и стены.
    Теперь мы можем переписать функцию
    CreateMaze с использованием этих фабричных методов:
    Maze* MazeGame::CreateMaze () {
    Maze* aMaze = MakeMaze();
    Room* r1 = MakeRoom(1);
    Room* r2 = MakeRoom(2);
    Door* theDoor = MakeDoor(r1, r2);
    aMaze->AddRoom(r1);
    aMaze->AddRoom(r2);
    r1->SetSide(North, MakeWall());
    r1->SetSide(East, theDoor);
    r1->SetSide(South, MakeWall());
    r1->SetSide(West, MakeWall());
    r2->SetSide(North, MakeWall());
    r2->SetSide(East, MakeWall());
    r2->SetSide(South, MakeWall());
    r2->SetSide(West, theDoor);
    return aMaze;
    }
    В играх могут порождаться различные подклассы
    MazeGame для специали- зации частей лабиринта. Эти подклассы могут переопределять некоторые
    (или все) методы, от которых зависят разновидности продуктов. Например, в игре
    BombedMazeGame продукты
    Room и
    Wall могут быть переопределены так, чтобы возвращать комнату и стену с заложенной бомбой:
    class BombedMazeGame : public MazeGame {
    public:
    BombedMazeGame();
    virtual Wall* MakeWall() const

    Паттерн Factory Method (фабричный метод)
    145
    { return new BombedWall; }
    virtual Room* MakeRoom(int n) const
    { return new RoomWithABomb(n); }
    };
    Вариация
    EnchantedMazeGame может определяться так:
    class EnchantedMazeGame : public MazeGame { public:
    EnchantedMazeGame(); virtual Room* MakeRoom(int n) const
    { return new EnchantedRoom(n, CastSpell()); } virtual Door* MakeDoor(Room* r1, Room* r2) const
    { return new DoorNeedingSpell(r1, r2); } protected:
    Spell* CastSpell() const;
    };
    Известные применения
    Фабричные методы в изобилии встречаются в инструментальных библио- теках и каркасах. Рассмотренный выше пример с документами — это типич- ное применение в каркасе MacApp и библиотеке ET++ [WGM88]. Пример с манипулятором заимствован из каркаса Unidraw.
    Класс
    View в схеме «модель — представление — контроллер» из языка Small- talk80 имеет метод defaultController
    , который создает контроллер, и этот ме- тод выглядит как фабричный [Par90]. Но подклассы
    View задают класс своего контроллера по умолчанию, определяя метод defaultControllerClass
    , воз- вращающий класс, экземпляры которого создает defaultController
    . Таким образом, реальным фабричным методом является defaultControllerClass
    , то есть метод, который должен переопределяться в подклассах.
    Более необычным является пример фабричного метода parserClass
    , тоже взятый из Smalltalk80, который определяется поведением
    Behavior
    (су- перкласс всех объектов, представляющих классы). Он позволяет классу использовать специализированный анализатор своего исходного кода.
    Например, клиент может определить класс
    SQLParser для анализа исход- ного кода класса, содержащего встроенные команды на языке SQL. Класс
    Behavior реализует parserClass так, что тот возвращает стандартный для
    Smalltalk класс анализатора
    Parser
    . Класс же, включающий предложения
    SQL, замещает этот метод (как метод класса) и возвращает класс
    SQLParser

    146
    Глава 3. Порождающие паттерны
    Система Orbix ORB от компании IONA Technologies [ION94] использует фабричный метод для генерирования подходящих заместителей (см. паттерн заместитель) в случае, когда объект запрашивает ссылку на удаленный объект.
    Фабричный метод позволяет без труда заменить подразумеваемого заместите- ля, например таким, который применяет кэширование на стороне клиента.
    Родственные паттерны
    Абстрактная фабрика (113) часто реализуется с помощью фабричных методов.
    Пример в разделе «Мотивация» из описания абстрактной фабрики иллюстри- рует также и паттерн фабричный метод.
    Паттерн фабричный метод часто вызывается внутри шаблонных методов
    (373). В примере с документами
    NewDocument
    — это шаблонный метод.
    Прототипы (146) не нуждаются в порождении подклассов от
    Creator
    . Однако им часто бывает необходима операция
    Initialize в классе
    Product
    Creator использует
    Initialize для инициализации объекта. Фабричному методу такая операция не требуется.
    ПАТТЕРН PROTOTYPE (ПРОТОТИП)
    Название и классификация паттерна
    Прототип — паттерн, порождающий объекты.
    Назначение
    Задает виды создаваемых объектов с помощью экземпляра-прототипа и соз- дает новые объекты путем копирования этого прототипа.
    Мотивация
    Построить музыкальный редактор удалось бы путем адаптации общего каркаса графических редакторов и добавления новых объектов, представ- ляющих ноты, паузы и нотный стан. В каркасе редактора может присутство- вать палитра инструментов для добавления в партитуру этих музыкаль- ных объектов. Палитра может также содержать инструменты для выбора, перемещения и иных манипуляций с объектами. Так, щелкая, например, по значку четверти, пользователь поместил бы тем самым четвертные ноты в партитуру. Или, применив инструмент перемещения, сдвигал бы ноту на стане вверх или вниз, чтобы изменить ее высоту.

    Паттерн Prototype (прототип)
    147
    Предположим, что каркас предоставляет абстрактный класс
    Graphic для графических компонентов вроде нот и нотных станов, а также абстрактный класс
    Tool для определения инструментов в палитре. Кроме того, в каркасе имеется предопределенный подкласс
    GraphicTool для инструментов, кото- рые создают графические объекты и добавляют их в документ.
    Однако класс
    GraphicTool создает проблему для проектировщика каркаса.
    Классы нот и нотных станов специфичны для нашего приложения, а класс
    GraphicTool принадлежит каркасу. Этому классу ничего неизвестно о том, как создавать экземпляры наших музыкальных классов и добавлять их в парти- туру. Можно было бы породить от
    GraphicTool подклассы для каждого вида музыкальных объектов, но тогда оказалось бы слишком много классов, отли- чающихся только тем, какой музыкальный объект они создают. Мы знаем, что гибкой альтернативой порождению подклассов является композиция. Вопрос в том, как каркас мог бы воспользоваться ею для параметризации экземпляров
    GraphicTool
    классом того объекта
    Graphic
    , который предполагается создать.
    Решение — заставить
    GraphicTool создавать новый графический объект копированием или «клонированием» экземпляра подкласса класса
    Graphic
    Этот экземпляр мы будем называть прототипом.
    GraphicTool параметризу- ется прототипом, который он должен клонировать и добавить в документ.
    Если все подклассы
    Graphic поддерживают операцию
    Clone
    , то
    GraphicTool может клонировать любой вид графических объектов.
    Итак, в нашем музыкальном редакторе каждый инструмент для создания музыкального объекта — это экземпляр класса
    GraphicTool
    , инициализирован- ный тем или иным прототипом. Любой экземпляр
    GraphicTool будет создавать музыкальный объект, клонируя его прототип и добавляя клон в партитуру.
    1   ...   8   9   10   11   12   13   14   15   ...   38


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