Паттерн Прототип.(из "Банды четырех")
Паттерн "Прототип" иллюстрируется в вышеуказанной книге следующим примером
Мотивация использования данного патерна - клиент не знает какой конкреный класс прототипа создавать или клиенту нужно иметь подкласс для каждого подкласса прототипа (создающий его), что не есть удобно.
В базовом классе прототипа определена функция Clone(), которую потом замещают конкретные классы (она создает и возвращает глубокую копию этого класса). Клиент владеет "прототипом" - базовым классом, потомков которого нужно создавать. Клиент узнает какой именно конкретный класс прототипа нужно инстанцировать в момент инициализации его КОНКРЕТНЫМ классом прототипа в конструкторе. А потом прототип (это уже конкретный класс) вызывает у себя функцию Clone() и получает копию себя же для дальнейших манипуляций. Мой вопрос: зачем вызывать функцию Clone() и копировать "прототип", если клиент еще в конструкторе уже получил конкретный прототип и можно с ним работать?
Ответы (2 шт):
Дам кусочек отсюда
Проблема
У вас есть объект, который нужно скопировать. Как это сделать? Нужно создать пустой объект такого же класса, а затем поочерёдно скопировать значения всех полей из старого объекта в новый.
Прекрасно! Но есть нюанс. Не каждый объект удастся скопировать таким образом, ведь часть его состояния может быть приватной, а значит — недоступной для остального кода программы.
Но есть и другая проблема. Копирующий код станет зависим от классов копируемых объектов. Ведь, чтобы перебрать все поля объекта, нужно привязаться к его классу. Из-за этого вы не сможете копировать объекты, зная только их интерфейсы, а не конкретные классы.
Решение
Паттерн Прототип поручает создание копий самим копируемым объектам. Он вводит общий интерфейс для всех объектов, поддерживающих клонирование. Это позволяет копировать объекты, не привязываясь к их конкретным классам. Обычно такой интерфейс имеет всего один метод clone.
Реализация этого метода в разных классах очень схожа. Метод создаёт новый объект текущего класса и копирует в него значения всех полей собственного объекта. Так получится скопировать даже приватные поля, так как большинство языков программирования разрешает доступ к приватным полям любого объекта текущего класса.
Объект, который копируют, называется прототипом (откуда и название паттерна). Когда объекты программы содержат сотни полей и тысячи возможных конфигураций, прототипы могут служить своеобразной альтернативой созданию подклассов.
Во-первых, клиент не обязательно «владеет» прототипом и не обязательно «получает его в конструкторе».
Суть прототипа в том, что клиент может создать копию известного объекта не заботясь о том что это за объект. Что клиент будет делать с этой копией и зачем она нужна — дело исключительно клиента. Он может использовать её сам, как временный объект, передать другому или оставить себе «на будущее», добавив в какой-либо контейнер, которым владеет.
Ключевая идея здесь в том, что операция копирования инкапсулируется в сам объект. Зачастую это настолько естественно и очевидно, что то, что используется какой-то «паттерн», и незаметно. Хотя иногда бывает и что-то более сложное.
В частности, для чистого порождения объектов прототипы часто применяют в составе «картатечной» фабрики, в качестве жизненного примера этот подход можно применить для создания объекта-энкодера по имени кодека, например как-то так:
class EncoderFactory() {
map<string, Encoder*> encoderCatalog;
EncoderFactory::EncoderFactory() {};
public:
void addEncoder(string name, Encoder * encoder) { encoderCatalog.insert(name,encoder);}
Encoder *encoderByName(string name) const {return encoderCatalog[name]->clone();}
static EncoderFactory &instance() {
static Factory f;
return f;
}
}
// ...
EncoderFactory::instance().addEncoder("mp3" , new MP3Encoder());
EncoderFactory::instance().addEncoder("acc" , new ACCEncoder());
VorbisEncoder *vorbis = new VorbisEncoder();
vorbis->setBitrate(196000); // установить битрейт по умолчанию
EncoderFactory::instance().addEncoder("vorbis" , vorbis);
// ...
string codec = getEncoderFromUser();
Encoder* encode = EncoderFactory::instance().encoderByName(codec);
encoder->encode(data);