Шаблон прототипа — это шаблон проектирования создания в разработке программного обеспечения . Он используется, когда типы создаваемых объектов определяются прототипическим экземпляром , который клонируется для создания новых объектов. Этот шаблон используется для того, чтобы избежать подклассов создателя объекта в клиентском приложении, как это делает шаблон метода фабрики , и избежать неотъемлемых затрат на создание нового объекта стандартным способом (например, с использованием ключевого слова ' new '), когда это непозволительно дорого для данного приложения.
Для реализации шаблона клиент объявляет абстрактный базовый класс , который определяет чистый виртуальный метод clone() . Любой класс, которому нужна возможность « полиморфного конструктора », выводится из абстрактного базового класса и реализует операцию clone() .
Клиент, вместо написания кода, который вызывает оператор «new» для жестко закодированного имени класса, вызывает метод clone() для прототипа, вызывает фабричный метод с параметром, обозначающим конкретный требуемый производный класс , или вызывает метод clone() через некий механизм, предоставляемый другим шаблоном проектирования.
Митотическое деление клетки, приводящее к двум идентичным клеткам, является примером прототипа, который играет активную роль в копировании самого себя и, таким образом, демонстрирует модель Прототипа. Когда клетка делится, в результате получаются две клетки с идентичным генотипом. Другими словами, клетка клонирует себя. [1]
Шаблон проектирования прототипа — один из 23 шаблонов проектирования «Банды четырех» , которые описывают, как решать повторяющиеся проблемы проектирования для разработки гибкого и повторно используемого объектно-ориентированного программного обеспечения, то есть объектов, которые легче внедрять, изменять, тестировать и повторно использовать. [2] : 117
Шаблон проектирования прототипа решает такие проблемы, как: [3]
Создание объектов непосредственно внутри класса, который требует (использует) объекты, негибко, поскольку привязывает класс к определенным объектам во время компиляции и делает невозможным указание того, какие объекты создавать во время выполнения.
Шаблон проектирования прототипа описывает, как решать такие проблемы:
Prototype
объект, который возвращает свою копию.Prototype
объект.Это позволяет конфигурировать класс с различными Prototype
объектами, которые копируются для создания новых объектов, и даже больше, Prototype
объекты могут быть добавлены и удалены во время выполнения.
См. также класс UML и диаграмму последовательности ниже.
В приведенной выше диаграмме классов UML класс ссылается на интерфейс для клонирования a . Класс реализует интерфейс, создавая копию самого себя.
Диаграмма последовательности UML показывает взаимодействия во время выполнения: Объект вызывает объект , который создает и возвращает копию самого себя ( объект).Client
Prototype
Product
Product1
Prototype
Client
clone()
prototype:Product1
product:Product1
Иногда шаблоны создания перекрываются — есть случаи, когда либо прототип, либо абстрактная фабрика были бы уместны. В других случаях они дополняют друг друга: абстрактная фабрика может хранить набор прототипов, из которых можно клонировать и возвращать объекты продукта. [2] : 126 Абстрактные фабрика, строитель и прототип могут использовать синглтон в своих реализациях. [2] : 81, 134 Абстрактные классы фабрики часто реализуются с помощью методов фабрики (создание через наследование ), но их можно реализовать с помощью прототипа (создание через делегирование ). [2] : 95
Часто проекты начинаются с использования метода Factory (менее сложный, более настраиваемый, подклассы разрастаются) и развиваются в сторону абстрактной фабрики, прототипа или конструктора (более гибкий, более сложный) по мере того, как проектировщик обнаруживает, где требуется больше гибкости. [2] : 136
Прототип не требует подклассификации, но требует операции «инициализации». Метод-фабрика требует подклассификации, но не требует инициализации. [2] : 116
Проекты, в которых активно используются композитные и декораторские шаблоны, также могут выиграть от использования Prototype. [2] : 126
Общее руководство в программировании предлагает использовать clone()
метод при создании дубликата объекта во время выполнения, чтобы гарантировать, что он точно отражает исходный объект. Этот процесс, известный как клонирование объекта, создает новый объект с идентичными атрибутами клонируемого объекта. В качестве альтернативы, создание экземпляра класса с использованием new
ключевого слова создает объект со значениями атрибутов по умолчанию.
Например, в контексте проектирования системы управления транзакциями по банковским счетам может потребоваться дублировать объект, содержащий информацию по счету, для проведения транзакций с сохранением исходных данных. В таких сценариях использование clone()
метода предпочтительнее, чем использование new
для создания нового объекта.
Эта реализация C++11 основана на реализации до C++98 в книге. Обсуждение шаблона проектирования вместе с полным иллюстративным примером реализации с использованием полиморфного проектирования классов приведены в аннотациях C++.
#include <iostream> enum Направление { Север , Юг , Восток , Запад }; класс MapSite { public : virtual void enter () = 0 ; virtual MapSite * clone () const = 0 ; virtual ~ MapSite () = default ; }; class Room : public MapSite { public : Room () : roomNumber ( 0 ) {} Room ( int n ) : roomNumber ( n ) {} void setSide ( Direction d , MapSite * ms ) { std :: cout << "Room::setSide" << d << ' ' << ms << '\n' ; } virtual void enter () {} virtual Room * clone () const { // реализует операцию для клонирования себя. return new Room ( * this ); } Room & Operator = ( const Room & ) = delete ; private : int roomNumber ; }; класс Стена : public MapSite { public : Стена () {} virtual void enter () {} virtual Стена * clone () const { return new Стена ( * this ); } }; class Door : public MapSite { public : Door ( Room * r1 = nullptr , Room * r2 = nullptr ) : room1 ( r1 ), room2 ( r2 ) {} Door ( const Door & other ) : room1 ( other . room1 ), room2 ( other . room2 ) {} virtual void enter () {} virtual Door * clone () const { return new Door ( * this ); } virtual void initialize ( Room * r1 , Room * r2 ) { room1 = r1 ; room2 = r2 ; } Door & Operator = ( const Door & ) = delete ; private : Room * room1 ; Room * room2 ; }; class Maze { public : void addRoom ( Room * r ) { std :: cout << "Maze::addRoom " << r << '\n' ; } Room * roomNo ( int ) const { return nullptr ; } virtual Maze * clone () const { return new Maze ( * this ); } virtual ~ Maze () = default ; }; класс MazeFactory { public : MazeFactory () = default ; virtual ~ MazeFactory () = default ; виртуальный лабиринт * makeMaze () const { return new лабиринт ; } виртуальная стена * makeWall () const { return new стена ; } виртуальная комната * makeRoom ( int n ) const { return new комната ( n ); } виртуальная дверь * makeDoor ( комната * r1 , комната * r2 ) const { return new дверь ( r1 , r2 ); } }; class MazePrototypeFactory : public MazeFactory { public : MazePrototypeFactory ( Maze * m , Wall * w , Room * r , Door * d ) : prototypeMaze ( m ), prototypeRoom ( r ), prototypeWall ( w ), prototypeDoor ( d ) {} virtual Maze * makeMaze () const { // создает новый объект, запрашивая у прототипа клонирование самого себя. return prototypeMaze -> clone (); } virtual Room * makeRoom ( int ) const { return prototypeRoom -> clone (); } virtual Wall * makeWall () const { return prototypeWall -> clone (); } virtual Door * makeDoor ( Room * r1 , Room * r2 ) const { Door * door = prototypeDoor -> clone (); door -> initialize ( r1 , r2 ); return door ; } MazePrototypeFactory ( const MazePrototypeFactory & ) = delete ; MazePrototypeFactory & оператор = ( const MazePrototypeFactory & ) = delete ; частный : Лабиринт * prototypeMaze ; Комната * prototypeRoom ; Стена * prototypeWall ; Дверь * prototypeDoor ; }; // Если createMaze параметризован различными прототипическими объектами комнаты, двери и стены, которые он затем копирует и добавляет в лабиринт, то вы можете изменить состав лабиринта, заменив эти прототипические объекты другими. Это пример шаблона Prototype (133).класс MazeGame { public : Maze * createMaze ( MazePrototypeFactory & m ) { Maze * aMaze = m . сделать лабиринт (); Комната * r1 = м . сделатьКомнату ( 1 ); Комната * r2 = м . сделатьКомнату ( 2 ); Дверь * Дверь = м . сделатьДверь ( r1 , r2 ); aMaze -> addRoom ( r1 ); aMaze -> addRoom ( r2 ); r1 -> setSide ( North , m.makeWall () ) ; r1 -> setSide ( Восток , Дверь ); r1 -> setSide ( Юг , м . makeWall ()); r1 -> setSide ( West , m.makeWall () ) ; r2 -> setSide ( North , m.makeWall () ) ; r2 -> setSide ( Восток , м . makeWall ()); r2 -> setSide ( Юг , м . makeWall ()); r2 -> setSide ( Запад , Дверь ); вернуть лабиринт ; } }; int main () { MazeGame game ; MazePrototypeFactory simpleMazeFactory ( новый лабиринт , новая стена , новая комната , новая дверь ) ; game.createMaze ( simpleMazeFactory ) ; }
Вывод программы:
Лабиринт :: addRoom 0x1160f50 Лабиринт :: addRoom 0x1160f70 Комната :: setSide 0 0x11613c0 Комната :: setSide 2 0x1160f90 Комната :: setSide 1 0x11613e0 Комната :: setSide 3 0x1161400 Комната :: setSide 0 0x1161420 Комната :: setSide 2 0x1161440 Комната :: setSide 1 0x1161460 Комната :: setSide 3 0x1160f90