Паттерн Прототип (Prototype) — уровень объекта

Название и классификация паттерна

Прототип — паттерн, порождающий объекты.

Назначение

Задает виды создаваемых объектов с помощью экземпляра-прото-типа и создает новые объекты путем копирования этого прототипа.

Паттерн Prototype (прототип) можно использовать в следующих случаях:

  • • система должна оставаться независимой как от процесса создания новых объектов, так и от типов порождаемых объектов. Непосредственное использование выражения new в коде приложения считается нежелательным;
  • • необходимо создавать объекты, точные классы которых становятся известными уже на стадии выполнения программы.

Паттерн Factory Method также делает систему независимой от типов порождаемых объектов, но для этого он вводит параллельную иерархию классов: для каждого типа создаваемого объекта должен присутствовать соответствующий класс-фабрика, что может быть нежелательно. Паттерн Prototype лишен этого недостатка.

Применимость

Используйте паттерн Прототип, когда система не должна зависеть от того, как в ней создаются, компонуются и представляются продукты:

  • • инстанцируемые классы определяются во время выполнения, например с помощью динамической загрузки;
  • • для того чтобы избежать построения иерархий классов или фабрик, параллельных иерархии классов продуктов;
  • • экземпляры класса могут находиться в одном из не очень большого числа различных состояний. Может оказаться удобнее установить соответствующее число прототипов и клонировать их, а не инстанцировать каждый раз класс вручную в подходящем состоянии.

Структура

Структура паттерна Прототип показана на рис. 39.

UML-диаграмма классов паттерна Prototype

Рис. 39. UML-диаграмма классов паттерна Prototype

Для порождения объекта некоторого типа в системе должен существовать его прототип. Прототип представляет собой объект того же типа, единственным назначением которого является создание подобных ему объектов. Обычно для удобства все существующие в системе прототипы организуются в специальные коллекции-хранилища или реестры прототипов. Такое хранилище может иметь реализацию в виде ассоциативного массива, каждый элемент которого представляет пару «Идентификатор типа» — «Прототип». Реестр прототипов позволяет добавлять или удалять прототип, а также создавать объект по идентификатору типа. Именно операции динамического добавления и удаления прототипов в хранилище обеспечивают дополнительную гибкость системе, позволяя управлять процессом создания новых объектов.

Участники

Prototype — прототип: объявляет интерфейс для клонирования самого себя.

ConcretePrototype — конкретный прототип: реализует операцию клонирования себя.

Client — клиент: создает новый объект, обращаясь к прототипу с запросом клонировать себя.

Отношения

Клиент обращается к прототипу, чтобы тот создал свою копию.

Результаты

У прототипа те же самые результаты, что у абстрактной фабрики и строителя: он скрывает от клиента конкретные классы продуктов, уменьшая тем самым число известных клиенту имен. Кроме того, все эти паттерны позволяют клиентам работать со специфичными для приложения классами без модификаций.

Ниже перечислены дополнительные преимущества паттерна Прототип:

  • добавление и удаление продуктов во время выполнения. Прототип позволяет включать новый конкретный класс продуктов в систему, просто сообщив клиенту о новом экземпляре-прототипе. Это несколько более гибкое решение по сравнению с тем, что удастся сделать с помощью других порождающих паттернов, ибо клиент может устанавливать и удалять прототипы во время выполнения программы;
  • спецификация новых объектов путем изменения значений. Динамичные системы позволяют определять поведение за счет композиции объектов, например путем задания значений переменных объекта, а не с помощью определения новых классов. По сути дела, вы определяете новые виды объектов, инстанцируя уже существующие классы и регистрируя их экземпляры как прототипы клиентских объектов. Клиент может изменить поведение, делегируя свои обязанности прототипу.

Такой дизайн позволяет пользователям определять новые классы без программирования. Фактически клонирование объекта аналогично инстанцированию класса. Паттерн Прототип может резко уменьшить число необходимых системе классов.

специфицирование новых объектов путем изменения структуры. Многие приложения строят объекты из крупных и мелких составляющих. Например, редакторы для проектирования печатных плат создают электрические схемы из подсхем. Такие приложения часто позволяют инстанцировать сложные, определенные пользователем структуры, скажем, для многократного использования некоторой подсхемы.

Паттерн Прототип поддерживает и такую возможность. Мы просто добавляем подсхему как прототип в палитру доступных элементов схемы. При условии, что объект, представляющий составную схему, реализует операцию Clone как глубокое копирование, схемы с разными структурами могут выступать в качестве прототипов;

  • уменьшение числа подклассов. Паттерн Фабричный метод часто порождает иерархию классов Creator, параллельную иерархии классов продуктов. Прототип позволяет клонировать прототип, а не запрашивать фабричный метод создать новый объект. Поэтому иерархия класса Creator становится вообще ненужной. Это преимущество касается главным образом языков типа C++, где классы не рассматриваются как настоящие объекты;
  • динамическое конфигурирование приложения классами. Некоторые среды позволяют динамически загружать классы в приложение во время его выполнения. Паттерн Прототип — это ключ к применению таких возможностей в языке типа C++. Приложение, которое создает экземпляры динамически загружаемого класса, не может обращаться к его конструктору статически. Вместо этого исполняющая среда автоматически создает экземпляр каждого класса в момент его загрузки и регистрирует экземпляр в диспетчере прототипов. Основной недостаток паттерна прототип заключается в том, что каждый подкласс класса Prototype должен реализовывать операцию Clone, а это далеко не всегда просто. Например, сложно добавить операцию Clone, когда рассматриваемые классы уже существуют. Проблемы возникают и в случае, если во внутреннем представлении объекта есть другие объекты или наличествуют круговые ссылки.

Реализация паттерна Prototype

Приведем реализацию паттерна Prototype на примере построения армий для военной стратегии «Пунические войны». Для упрощения

демонстрационного кода будем создавать военные персонажи для некой абстрактной армии без учета особенностей воюющих сторон. Приведем две возможные реализации паттерна Prototype, а именно:

  • • в виде обобщенного конструктора на основе прототипов, когда в полиморфном базовом классе Prototype определяется статический метод, предназначенный для создания объектов. При этом в качестве параметра в этот метод должен передаваться идентификатор типа создаваемого объекта;
  • • на базе специально выделенного класса-фабрики.

Реализация паттерна Ptototype на основе обобщенного конструктора

#include

#include

#include

// Идентификаторы всех родов войск

enum Warrior_ID {Infantryman_ID, Archer_ID, Horseman_ID };

class Warrior; // Опережающее объявление typedef map Registry;

// Реестр прототипов определен в виде Singleton Мэйерса Registry& getRegistryO {

static Registry _instance; return _instance;

}

// Единственное назначение этого класса — помощь в выборе нужного // конструктора при создании прототипов class Dummy {};

// Полиморфный базовый класс. Здесь также определен статический // обобщенный конструктор для создания боевых единиц всех // родов войск class Warrior {

public:

virtual Warrior* clone() = 0; virtual void info() = 0; virtual ~Warrior() {}

// Параметризированный статический метод для создания воинов // всех родов войск

static Warrior* createWarrior( Warrior_ID id ) {

Registry & г = getRegistry(); if (r.find(id) != r.end()) return r[id|->clone(); return 0;

}

protected:

// Добавление прототипа в множество прототипов

static void addPrototype( Warrior_ID id, Warrior * prototype ) { Registry& r = getRegistry(); r[id] = prototype;

}

//Удаление прототипа из множества прототипов

static void removePrototype( Warrior_ID id ) {

Registry& r = getRegistry(); r.erase( r.find( id));

}

};

// В производных классах различных родов войск в виде статических // членов-данных определяются соответствующие прототипы class Infantryman: public Warrior {

public:

Warrior* clone() { return new Infantryman( *this);

}

void info() {

cout << "Infantryman" << endl;

}

private:

Infantryman( Dummy) {

Warrior::addPrototype( Infantryman_ID, this);

}

Infantryman() {}

static Infantryman prototype;

};

class Archer: public Warrior

{

public:

Warrior* clone() { return new Archer( *this);

}

void info() {

cout << "Archer" << endl;

}

private:

Archer( Dummy) { addPrototype( Archer_ID, this);

}

Archer() {}

static Archer prototype;

};

class Horseman: public Warrior

{

public:

Warrior* clone() { return new Horseman( *this);

}

void info() {

cout << "Horseman" << endl;

}

private:

Horseman( Dummy) { addPrototype( Horseman_ID, this);

}

Horseman() {}

static Horseman prototype;

};

Infantryman Infantryman:prototype = Infantryman! Dummy!));

Archer Archer: prototype = Archer! Dummy!));

Horseman Horseman::prototype = Horseman! Dummy!));

int main!)

{

vector< Warrior* > v;

v.push_back( Warrior::createWarrior( Infantryman_ID)); v.push_back( Warrior::createWarrior! Archer lD)); v.push_back( Warrior::createWarrior( Horseman_ID));

for(int i=0; iinfo();

//...

}

В приведенной реализации классы всех создаваемых военных единиц, таких как лучники, пехотинцы и конница, являются подклассами абстрактного базового класса Warrior. В этом классе определен обобщенный конструктор в виде статического метода create Warrior(Warrior_I D id). Передавая в этот метод в качестве параметра тип боевой единицы, можно создавать воинов нужных родов войск. Для этого обобщенный конструктор использует реестр прототипов, реализованный в виде ассоциативного массива stdr.map, каждый элемент которого представляет собой пару «идентификатор типа воина» — «его прототип».

Добавление прототипов в реестр происходит автоматически. Сделано это следующим образом. В подклассах Infantryman, Archer, Horseman прототипы определяются в виде статических членов данных тех же типов. При создании такого прототипа будет вызываться конструктор с параметром типа Dummy, который и добавит этот прототип в реестр прототипов с помощью метода add Prototype!) базового класса Warrior. Важно, чтобы к этому моменту сам объект реестра был полностью сконструирован, именно поэтому он выполнен в виде singleton Мэйерса.

Для приведенной реализации паттерна Prototype можно отметить следующие особенности:

Создавать новых воинов можно только при помощи обобщенного конструктора. Их непосредственное создание невозможно, так как соответствующие конструкторы объявлены со спецификатором доступа private.

Отсутствует недостаток реализации на базе обобщенного конструктора для паттерна Factory Method, а именно базовый класс Warrior ничего не знает о своих подклассах.

Реализация паттерна Prototype с помощью выделенного класса-фабрики

#include

#include

// Иерархия классов игровых персонажей

// Полиморфный базовый класс

class Warrior

{

public:

virtual Warrior* clone() = 0; virtual void info() = 0; virtual ~Warrior() {}

// Производные классы различных родов войск class Infantryman: public Warrior {

friend class PrototypeFactory; public:

Warrior* clone() { return new Infantryman( *this);

}

void info() {

cout << "Infantryman" << endl;

}

private:

Infantryman() {}

};

class Archer: public Warrior

{

friend class PrototypeFactory; public:

Warrior* clone() { return new Archer( *this);

}

void info() {

cout << "Archer" << endl;

}

private:

Archer() {}

};

class Horseman: public Warrior

{

friend class PrototypeFactory; public:

Warrior* clone() { return new Horseman( *this);

}

void info() {

cout << "Horseman" << endl;

}

private:

Horseman() {}

// Фабрика для создания боевых единиц всех родов войск class Prototype Factory {

public:

Warrior* createlnfantrman() { static Infantryman prototype; return prototype.clone();

}

Warrior* createArcher() { static Archer prototype; return prototype.clone();

}

Warrior* createHorseman() { static Horseman prototype; return prototype.clone();

}

};

int main()

{

Prototype Factory factory; vector< Warrior* > v;

v.push_back( factory.createlnfantrman()); v.push_back( factory.createArcher()); v.push_back( factory.createHorseman());

for(int i=0; iinfo();

//...

}

В приведенной реализации для упрощения кода реестр прототипов не ведется. Воины всех родов войск создаются при помощи соответствующих методов фабричного класса Prototype Factory, где и определены прототипы в виде статических переменных.

Результаты применения паттерна Prototype

Достоинства паттерна Prototype

Для создания новых объектов клиенту необязательно знать их конкретные классы.

Возможность гибкого управления процессом создания новых объектов за счет возможности динамических добавления и удаления прототипов в реестр.

Недостатки паттерна Prototype

Каждый тип создаваемого продукта должен реализовывать операцию клонирования clone(). В случае если требуется глубокое копирование объекта (объект содержит ссылки или указатели на другие объекты), это может быть непростой задачей.

Родственные паттерны

В некоторых отношениях прототип и абстрактная фабрика являются конкурентами. Но их используют и совместно. Абстрактная фабрика может хранить набор прототипов, которые клонируются и возвращают изготовленные объекты.

В тех проектах, где активно применяются паттерны Компоновщик и Декоратор, тоже можно извлечь пользу из прототипа.

 
< Пред   СОДЕРЖАНИЕ     След >