Шаблон проектирования прототипов — это порождающий шаблон проектирования, который помогает в прототипировании (недорогом создании/копировании) объекта с использованием отдельных методов или полиморфных классов. Прототипом можно считать модель объекта, на основе которой будет построен реальный объект. В этой статье о порождающих шаблонах проектирования мы разберёмся, для чего нужен шаблон проектирования прототипов в C++, рассмотрим фабрику прототипов и использование шаблона проектирования прототипов для реализации виртуального конструктора копирования.
В статье использованы упрощённые фрагменты кода. Так, например, вы можете заметить, что я часто не использую такие ключевые слова, как override
, final
, public
(когда речь идёт о наследовании), ради большей компактности кода, чтобы он элементарно помещался у вас на экране. Кроме того, я иногда предпочитаю использовать struct
вместо class
, просто чтобы сэкономить строчку и не писать public:
, и опускаю виртуальный деструктор, конструктор, конструктор копирования, префикс std::
, намеренно убирая динамическую память. Я считаю себя прагматичным человеком и стремлюсь донести свои мысли самым простым способом, а не умничать, говоря на непонятном языке.
Внимание:
boost
.Создать новый объект, который будет недорогим за счёт использования уже построенного или предварительно инициализированного и сохранённого объекта.
struct Office {
string m_street;
string m_city;
int32_t m_cubical; Office(string s, string c, int32_t n):m_street(s), m_city(c), m_cubical(n){}
};struct Employee {
string m_name;
Office m_office; Employee(string n, Office o):m_name(n), m_office(o){}
};int main() {
Employee john{ "John Doe", Office{"123 East Dr", "London", 123} };
Employee jane{ "Jane Doe", Office{"123 East Dr", "London", 124} };
Employee jack{ "jack Doe", Office{"123 ORR", "Bangaluru", 300} };
return EXIT_SUCCESS;
}
struct Employee {
string m_name;
const Office* m_office;
Employee(string n, Office *o):m_name(n), m_office(o){}
};
static Office LondonOffice{"123 East Dr", "London", 123};
static Office BangaluruOffice{"RMZ Ecoworld ORR", "London", 123};
int main() {
Employee john{ "John Doe", &LondonOffice };
Employee jane{ "Jane Doe", &LondonOffice };
Employee jack{ "jack Doe", &BangaluruOffice };
return EXIT_SUCCESS;
}
equals
просто-напросто не работает.В предыдущем примере шаблона проектирования прототипа у нас был глобальный объект для адреса офиса, который мы использовали для создания прототипов.
struct Office {
string m_street;
string m_city;
int32_t m_cubical;
};
class Employee {
string m_name;
Office* m_office;
// Закрытый конструктор, поэтому прямой экземпляр не может быть создан, кроме `class EmployeeFactory`
Employee(string n, Office *o) : m_name(n), m_office(o) {}
friend class EmployeeFactory;
public:
Employee(const Employee &rhs) : m_name{rhs.m_name}, m_office{new Office{*rhs.m_office}}
{ }
Employee& operator=(const Employee &rhs) {
if (this == &rhs) return *this;
m_name = rhs.m_name;
m_office = new Office{*rhs.m_office};
return *this;
}
friend ostream &operator<<(ostream &os, const Employee &o) {
return os << o.m_name << " works at "
<< o.m_office->m_street << " " << o.m_office->m_city << " seats @" << o.m_office->m_cubical;
}
};
class EmployeeFactory {
static Employee main;
static Employee aux;
static unique_ptr<Employee> NewEmployee(string n, int32_t c, Employee &proto) {
auto e = make_unique<Employee>(proto);
e->m_name = n;
e->m_office->m_cubical = c;
return e;
}
public:
static unique_ptr<Employee> NewMainOfficeEmployee(string name, int32_t cubical) {
return NewEmployee(name, cubical, main);
}
static unique_ptr<Employee> NewAuxOfficeEmployee(string name, int32_t cubical) {
return NewEmployee(name, cubical, aux);
}
};
// Инициализация статического члена
Employee EmployeeFactory::main{"", new Office{"123 East Dr", "London", 123}};
Employee EmployeeFactory::aux{"", new Office{"RMZ Ecoworld ORR", "London", 123}};
int main() {
auto jane = EmployeeFactory::NewMainOfficeEmployee("Jane Doe", 125);
auto jack = EmployeeFactory::NewAuxOfficeEmployee("jack Doe", 123);
cout << *jane << endl << *jack << endl;
return EXIT_SUCCESS;
}
/*
Марьиванна работает по адресу 123 East Dr London seats @125
Вася Пупкин работает по адресу RMZ Ecoworld ORR London seats @123
*/
Employee
и friend EmployeeFactory
. Так мы добиваемся того, чтобы клиент/API-пользователь создавал экземпляр Employee
только через EmployeeFactory
.struct animal {
virtual ~animal(){ cout<<"~animal\n"; }
};
struct dog : animal {
~dog(){ cout<<"~dog\n"; }
};
struct cat : animal {
~cat(){ cout<<"~cat\n"; }
};
void who_am_i(animal *who) { // не известно, будет ли здесь собака или кошка
// Как `создать` объект того же типа, т.е. обозначаемый словом who (кто)?
// Как `скопировать` объект того же типа, т.е. обозначаемый словом who (кто)?
delete who; // вы можете удалить соответствующий объект, обозначаемый словом who (кто), благодаря виртуальному деструктору
}
clone()
), но и виртуальный конструктор (т. е. create()
).struct animal {
virtual ~animal() = default;
virtual std::unique_ptr<animal> create() = 0;
virtual std::unique_ptr<animal> clone() = 0;
};
struct dog : animal {
std::unique_ptr<animal> create() { return std::make_unique<dog>(); }
std::unique_ptr<animal> clone() { return std::make_unique<dog>(*this); }
};
struct cat : animal {
std::unique_ptr<animal> create() { return std::make_unique<cat>(); }
std::unique_ptr<animal> clone() { return std::make_unique<cat>(*this); }
};
void who_am_i(animal *who) {
auto new_who = who->create();// `создать` объект того же типа, т.е. обозначаемый словом who (кто)?
auto duplicate_who = who->clone(); // `скопировать` объект того же типа, т.е. обозначаемый словом who (кто)?
delete who;
}
Вопрос: для чего нужен шаблон проектирования прототипов?
Вопрос: шаблон проектирования прототипов — на самом деле просто клон?
Нет, это не так, если использовать его вместе с фабричным шаблоном проектирования.
Вопрос: шаблон проектирования прототипов используется, когда создавать новый объект дорого, но мы же создаём в clone().
Вы, должно быть, обратили внимание на то, что в разделе статьи, посвящённой фабрике прототипов, мы создаём экземпляры в конструкторе копирования. Разве это дорого? Тогда представьте себе HTTP-запрос: его заголовок состоит из версии, типа кодировки, типа содержимого, типа сервера и т. д. Сначала вам нужно найти эти параметры с помощью вызовов соответствующих функций. Но параметры не меняются, пока соединение не закрыто. Поэтому есть ли смысл снова и снова выполнять вызовы функций для получения этих параметров? Дорого обходятся нам не параметры, а их функции для получения значения.
Перевод статьи Vishal Chovatiya: Prototype Design Pattern in Modern C++
Комментарии