Prototype Design Pattern is a Creational Design Pattern that helps in the prototyping(creating/copying cheaply) of an object using separate methods or polymorphic classes. You can consider the prototype as a template of an object before the actual object is constructed. In this article of the Creational Design Patterns, we’re going to take a look at why we need a Prototype Design Pattern in C++ i.e. motivation, prototype factory & leveraging prototype design pattern to implement virtual copy constructor.
By the way, If you haven’t check out my other articles on Creational Design Patterns, then here is the list:
The code snippets you see throughout this series of articles are simplified not sophisticated. So you often see me not using keywords like override, final, public(while inheritance) just to make code compact & consumable(most of the time) in single standard screen size. I also prefer struct instead of class just to save line by not writing “public:” sometimes and also miss virtual destructor, constructor, copy constructor, prefix std::, deleting dynamic memory, intentionally. I also consider myself a pragmatic person who wants to convey an idea in the simplest way possible rather than the standard way or using Jargons.
Note:
To create a new object cheaply with the help of an already constructed or pre-initialized stored object.
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;
}
struct Office {
string m_street;
string m_city;
int32_t m_cubical;
};
class Employee {
string m_name;
Office* m_office;
// Private constructor, so direct instance can not be created except for `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);
}
};
// Static Member Initialization
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;
}
/*
Jane Doe works at 123 East Dr London seats @125
jack Doe works at RMZ Ecoworld ORR London seats @123
*/
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) { // not sure whether dog would be passed here or cat
// How to `create` the object of same type i.e. pointed by who ?
// How to `copy` object of same type i.e. pointed by who ?
delete who; // you can delete appropriate object pointed by who, thanks to virtual destructor
}
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();// `create` the object of same type i.e. pointed by who ?
auto duplicate_who = who->clone(); // `copy` object of same type i.e. pointed by who ?
delete who;
}
What’s the point of using the Prototype Design Pattern?
Is the Prototype Design Pattern Really Just Clone?
It isn’t if you combine it with the Factory Design Pattern.
Prototype design pattern to be used when creation is costly, but we do create in the clone.
You must be wondering that in Prototype Factory we show above, we are creating instances in the copy constructor. Isn’t that expensive. Yes, it is. But just think about HTTP request, its header consist version, encoding type, content type, server-type, etc.
Initially, you need a find out these parameters using respective function calls. But once you got these, these are not going to change until connection closed. So there is no point in doing function calls to extract these params over & over. What cost us here is not parameters but their functions to extract value.
Previously published at http://www.vishalchovatiya.com/prototype-design-pattern-in-modern-cpp/