1.4 Prototype (Creational) Design Pattern

The Prototype Design Pattern serves as a powerful tool, particularly in scenarios where object creation is resource-intensive or requires intricate initialization. By leveraging the Prototype Pattern, developers can circumvent the overhead of creating new objects from scratch, opting instead to clone existing objects and tailor them to specific needs.

1 Understanding the Prototype Pattern

The Prototype Design Pattern is a creational pattern that enables the creation of new objects by copying an existing object. Prototype allows us to hide the complexity of making new instances from the client. The concept is to copy an existing object rather than create a new instance from scratch, something that may include costly operations. The existing object acts as a prototype and contains the state of the object.

  • The newly copied object may change the same properties only if required. This approach saves costly resources and time, especially when object creation is a heavy process.
  • The prototype pattern is a creational design pattern. Prototype patterns are required when object creation is a time-consuming, and costly operation, so we create objects with the existing object itself.
  • One of the best available ways to create an object from existing objects is the clone() method. Clone is the simplest approach to implementing a prototype pattern. However, it is your call to decide how to copy existing objects based on your business model.

2 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

The Prototype Interface or Abstract Class declares the method(s) for cloning an object. It defines the common interface that concrete prototypes must implement, ensuring that all prototypes can be cloned in a consistent manner.

  • The main role is to provide a blueprint for creating new objects by specifying the cloning contract.
  • It declares the clone method, which concrete prototypes implement to produce copies of themselves.

#2 Concrete Prototype

The Concrete Prototype is a class that implements the prototype interface or extends the abstract class. It’s the class representing a specific type of object that you want to clone.

  • It defines the details of how the cloning process should be carried out for instances of that class.
  • Implements the clone method declared in the prototype interface, providing the cloning logic specific to the class.

#3 Client

The Client is the code or module that requests the creation of new objects by interacting with the prototype. It initiates the cloning process without being aware of the concrete classes involved.

#4 Clone Method

The Clone Method is declared in the prototype interface or abstract class. It specifies how an object should be copied or cloned. Concrete prototypes implement this method to define their unique cloning behavior. It Describes how the object’s internal state should be duplicated to create a new, independent instance.

3 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;
}