The Prototype Design Pattern is a creational pattern that allows you to create new objects by copying an existing object, known as the "prototype," rather than creating a new instance from scratch.
This pattern is useful when the cost of creating a new object is more expensive than cloning an existing one, or when you need to create objects with similar states quickly.
Prototype pattern creates duplicate objects while keeping performance in mind. It provides a mechanism to copy the original object to a new one without making the code dependent on their classes
In simpler terms:
Imagine you already have a perfectly set-up object — like a well-written email template or a configured game character. Instead of building a new one every time (which can be repetitive and expensive), you just copy the existing one and make small adjustments. This is what the Prototype Pattern does. It allows you to create new objects by copying existing ones, saving time and resources.
Real-World Analogy
Think of document templates. Instead of creating a new document from scratch every time, you clone a pre-filled template and modify it.
OR
Think of preparing ten offer letters. Instead of typing the same letter ten times, you write it once, photocopy it, and change just the name on each copy. This is how the Prototype Pattern works: start with a base object and produce modified copies with minimal changes.
When to Use
- Object creation is resource-intensive or complex.
- You require many similar objects with slight variations.
- You want to avoid writing repetitive initialization logic.
- You need runtime object creation without tight class coupling.
Components of Prototype Design Pattern
The Prototype Design Pattern’s components include the prototype interface or abstract class, concrete prototypes and the client code, and the clone method specifying cloning behavior. These components work together to enable the creation of new objects by copying existing ones.
1 Prototype Interface or Abstract Class
- Declares the
clone()
method. - This is the common interface for all concrete prototypes.
class Prototype {
public:
virtual std::unique_ptr<Prototype> clone() const = 0;
virtual void use() = 0;
virtual ~Prototype() {}
};
2 Concrete Prototype
- Implements the
clone()
method by returning a copy of itself. - Each subclass knows how to duplicate its data properly.
class ConcretePrototypeA : public Prototype {
private:
int data;
public:
ConcretePrototypeA(int d) : data(d) {}
std::unique_ptr<Prototype> clone() const override {
return std::make_unique<ConcretePrototypeA>(*this);
}
void use() override {
std::cout << "Using ConcretePrototypeA with data: " << data << "\n";
}
};
3 Client
- Uses the
clone()
method to create new objects from existing ones. - The client is decoupled from the concrete class of the object.
int main() {
std::unique_ptr<Prototype> prototype = std::make_unique<ConcretePrototypeA>(100);
std::unique_ptr<Prototype> clone1 = prototype->clone();
clone1->use(); // Output: Using ConcretePrototypeA with data: 100
std::unique_ptr<Prototype> clone2 = prototype->clone();
clone2->use();
}
Implementation in C++
#include <iostream>
#include <string>
#include <unordered_map>
// Prototype Interface
class DocumentPrototype {
public:
virtual DocumentPrototype* clone() const = 0;
virtual void fillContent(const std::string& content) = 0;
virtual void display() const = 0;
virtual ~DocumentPrototype() {}
};
// Concrete Prototypes: Resume
class Resume : public DocumentPrototype {
private:
std::string content;
public:
DocumentPrototype* clone() const override {
return new Resume(*this);
}
void fillContent(const std::string& content) override {
this->content = content;
}
void display() const override {
std::cout << "Resume Content: " << content << std::endl;
}
};
// Concrete Prototypes: Report
class Report : public DocumentPrototype {
private:
std::string content;
public:
DocumentPrototype* clone() const override {
return new Report(*this);
}
void fillContent(const std::string& content) override {
this->content = content;
}
void display() const override {
std::cout << "Report Content: " << content << std::endl;
}
};
// Prototype Factory
class DocumentPrototypeFactory {
private:
std::unordered_map<std::string, DocumentPrototype*> prototypes;
public:
void registerPrototype(const std::string& key, DocumentPrototype* prototype) {
prototypes[key] = prototype;
}
DocumentPrototype* clone(const std::string& key) const {
if (prototypes.find(key) != prototypes.end()) {
return prototypes.at(key)->clone();
}
return nullptr;
}
};
int main() {
// Create prototype objects
Resume resumePrototype;
Report reportPrototype;
// Register prototypes with the factory
DocumentPrototypeFactory factory;
factory.registerPrototype("resume", &resumePrototype);
factory.registerPrototype("report", &reportPrototype);
// Clone prototypes to create new documents
DocumentPrototype* newResume = factory.clone("resume");
DocumentPrototype* newReport = factory.clone("report");
// Customize documents
if (newResume) {
newResume->fillContent("John Doe\nSoftware Engineer");
newResume->display();
delete newResume;
}
if (newReport) {
newReport->fillContent("Sales Report\nQuarter 1");
newReport->display();
delete newReport;
}
return 0;
}