Prototype Design Pattern In C++ - C++ Programming Tutorial
C++ Course / Design Patterns / Prototype Design Pattern In C++

Prototype Design Pattern In C++

BLUF: Mastering Prototype Design Pattern In C++ is a critical step in becoming a proficient C++ developer. This lesson provides a deep dive into the syntax, performance considerations, and real-world applications of this concept.
Key Performance Insight: Prototype Design Pattern In C++

C++ is renowned for its efficiency. Learn how Prototype Design Pattern In C++ enables low-level control and high-performance computing in the tutorial below.

A prototype Design Pattern is a creational design pattern that enables the generation of fresh objects by duplicating an existing object 'prototype' rather than relying on constructors. This pattern is particularly beneficial in scenarios where creating objects consumes significant resources, involves lengthy processes, or demands intricate configurations. In contrast to conventional object creation using the new object instantiation method, this pattern leverages the prototype object to produce new objects that are either identical copies or slightly modified replicas.

The Prototype Pattern holds significance in specific scenarios: when generating objects based on conditions, and when objects share similar parameters during creation. This pattern allows developers to utilize existing objects as blueprints for new ones, thereby minimizing the need for repeated type initialization.

Essentially, the Prototype Pattern enables the client code to operate independently of the object creation class. This is achieved by employing a simple copying mechanism that circumvents the object's concrete implementation. This level of abstraction not only liberates the system from inherent dependencies but also adheres to the open/closed principle, allowing for system expansion and enhancements while restricting modifications.

This specific design methodology is frequently employed in situations where rapid performance is vital due to the minimal cost associated with object creation. For instance, within graphic software, it is more efficient to duplicate a shape with predetermined attributes rather than constructing shapes anew. Similarly, in scenarios where initializing applications requires access to databases, files, or other resources, the Prototype Pattern eliminates the necessity of repeatedly initializing numerous objects for improved efficiency.

Another key advantage of the Prototype Pattern lies in its effectiveness for creating objects with minor variations. Modifying the cloned instance without impacting the prototype allows developers to customize the original object without compromising the prototype. This prevents any potential damage to the original prototype while providing a reliable starting point for customization.

Core Components of the Prototype Pattern

The Prototype Design Pattern is centered on three key elements: The initial component is known as the Prototype Interface, the second one is the Concrete Prototype, and the final one is the Client Code. Together, these elements facilitate efficient object duplication without the client having a direct dependency on a specific object class.

1. Prototype Interface

The Prototype Interface serves as the initial component within the pattern. It establishes an agreement ensuring that all classes implementing it can generate their individual copies. This interface typically includes a cloning method, which is employed to replicate the object. Essentially, by abstracting the object cloning process, the interface protects the client code from the intricacies involved in creating the clone.

The duplication process can vary in precision, either being precise or imprecise, resulting in a basic or thorough duplication. A byte-for-byte replication solely duplicates the attributes within the object's hierarchy, retaining connections to external objects. This method is quicker but comes with consequences, particularly when the duplicated object accesses changeable resources. On the other hand, a comprehensive duplication generates a replica of every embedded object, ensuring no connection exists between the duplicated entities and the initial ones. The choice between a superficial or thorough duplication hinges on the specific requirements of the application and the nature of the prototype under consideration.

2. Concrete Prototype

A specific model in physical form outlines the Prototype Interface and is responsible for the duplication process. It encapsulates the information and actions that the consumer aims to imitate. The replication procedure may vary based on the object, and in cases where the object's data structure or relationships are intricate, the Concrete Prototype class is responsible for defining the copying mechanism.

For instance, within a hierarchical object structure, a specific Prototype must duplicate its offspring objects in order to accurately replicate the copied version. This adds a layer of intricacy to the cloning process, but enhances its feasibility by preserving the original architectural design and operational framework.

The Concrete Prototype class functions as a model for the client, allowing the creation of new objects for the class without relying directly on the class itself. This enhances the system's modularity, simplifying maintenance and scalability.

3. Client Code

The consumer code interacts with the Prototype Interface to generate a comparable object using duplicates of prototypes. This approach ensures that the consumer is unaware of the specific implementation of the prototype and can only interact with the conceptual interface. This separation not only enhances adaptability but also empowers the consumer to manage various types of prototypes effortlessly.

In a standard setup, the client oversees a registry or a set of prototypes that vary slightly from the approach in the actor model. Another registry serves as a centralized store for predefined templates, each tailored to a particular object type. When the client requires a new object, they access the registry, acquire the appropriate prototype, and duplicate it. Afterward, the client customizes the duplicated object according to their requirements, while the prototype remains unchanged.

What sets the Prototype Pattern apart is its foundation on cloning, a unique feature in object creation not commonly found in other creational design patterns. In contrast to the Factory method or the Abstract Factory patterns, which primarily focus on creating new objects by inheriting concrete classes, the Prototype Pattern revolves around object creation through cloning. This approach proves beneficial in scenarios where applications require generating objects with intricate initialization processes or dynamic configurations.

Advantages of Using the Prototype Pattern

All in all, the Prototype Design Pattern seems to present several clear benefits, especially when dealing with issues of object creation that must be both effective and unsolicited by particular classes of objects. Due to its ability to avoid cloning and focus on creating new objects from scratch, the pattern solves various problems in software development. Below are the key advantages of using the Prototype Pattern:

  • Efficient Object Creation An advantage of the Prototype Pattern, as already discussed, is that objects of any class can be created quickly. When constructing objects requires considerable resource utilization, for instance, initializing large data sets, connecting to databases, and loading external resources, the Prototype Pattern does not require a complete object construction process to be repeated. Instead, it brings into being a new object through duplication of an existing object, a process that is much more efficient in most cases. This efficiency is the most helpful in the case of performance-critical systems, in video games, simulations, real-time applications, and so on, where the creation of multiple instances of far from trivial objects is necessarily fast. For instance, in a game, instead of developing new instances of application-specific objects, such as enemy characters with identical attributes/properties, the system can replicate a prototype.
  • Simplified Object Initialization The complexity is involved when objects want to be created with a significant amount of initialization logic, where Prototype Pattern is useful. The Prototype Pattern is particularly useful where the new objects to be developed involve complex creation processes. Rather than incorporating complex setup processes directly into the object's creation mechanism, architects have the opportunity to place the configurations into a prototype. In order to create a new instance, the system creates a copy of the prototype so the configuration it contains remains fixed. This makes creation easier and cuts down on the issue of members copying code as they work. Also, it enhances the known principle of separation of concern. The initialization details are encapsulated in the prototype, and the client code does not contain complex code for object creation. It also makes the system more modular in addition to making it simpler to debug and maintain than if everything were intertwined.
  • Support for Dynamic Object Creation However, in some cases, it may be unknown at compile time what exact type of object has to be created. The idea of the Prototype Pattern is more universal since objects can be created at runtime through cloning. The system can also keep a record of a set of prototypes where each prototype reflects a corresponding type of the object in question. If the client requires a new instance, it pulls the correct prototype from the registry and then copies it. This dynamic approach can be of great worth in such scenarios, especially in the frameworks or libraries that require having an extensibility system. For example, there could be a graphical application for creating shapes, and a user could decide which shapes will be loaded. As a result, they can register custom prototypes of shapes, and instead of hardcoding the creation process for each shape type, the application can clone them.
  • Independence from Concrete Classes Evaluating the construction of the new object independently from concrete classes, the Prototype Pattern follows the principles of abstraction and modularity. The client works only with the interface of the prototype, being unaware of the actual class of an object being copied. It also allows for change of kinds of prototypes introduced to the system more freely as they do not affect client code. It also is consistent with another elementary rule of software design, the Open/Closed Principle. The extensibility of the system can be done with new prototypes incorporating this Feature space without touching the existing code and introducing new bugs.
  • An advantage of the Prototype Pattern, as already discussed, is that objects of any class can be created quickly. When constructing objects requires considerable resource utilization, for instance, initializing large data sets, connecting to databases, and loading external resources, the Prototype Pattern does not require a complete object construction process to be repeated. Instead, it brings into being a new object through duplication of an existing object, a process that is much more efficient in most cases.
  • This efficiency is the most helpful in the case of performance-critical systems, in video games, simulations, real-time applications, and so on, where the creation of multiple instances of far from trivial objects is necessarily fast. For instance, in a game, instead of developing new instances of application-specific objects, such as enemy characters with identical attributes/properties, the system can replicate a prototype.
  • The complexity is involved when objects want to be created with a significant amount of initialization logic, where Prototype Pattern is useful.
  • The Prototype Pattern is particularly useful where the new objects to be developed involve complex creation processes. Rather than incorporating complex setup processes directly into the object's creation mechanism, architects have the opportunity to place the configurations into a prototype. In order to create a new instance, the system creates a copy of the prototype so the configuration it contains remains fixed. This makes creation easier and cuts down on the issue of members copying code as they work.
  • Also, it enhances the known principle of separation of concern. The initialization details are encapsulated in the prototype, and the client code does not contain complex code for object creation. It also makes the system more modular in addition to making it simpler to debug and maintain than if everything were intertwined.
  • However, in some cases, it may be unknown at compile time what exact type of object has to be created. The idea of the Prototype Pattern is more universal since objects can be created at runtime through cloning. The system can also keep a record of a set of prototypes where each prototype reflects a corresponding type of the object in question. If the client requires a new instance, it pulls the correct prototype from the registry and then copies it.
  • This dynamic approach can be of great worth in such scenarios, especially in the frameworks or libraries that require having an extensibility system. For example, there could be a graphical application for creating shapes, and a user could decide which shapes will be loaded. As a result, they can register custom prototypes of shapes, and instead of hardcoding the creation process for each shape type, the application can clone them.
  • Evaluating the construction of the new object independently from concrete classes, the Prototype Pattern follows the principles of abstraction and modularity. The client works only with the interface of the prototype, being unaware of the actual class of an object being copied. It also allows for change of kinds of prototypes introduced to the system more freely as they do not affect client code.
  • It also is consistent with another elementary rule of software design, the Open/Closed Principle. The extensibility of the system can be done with new prototypes incorporating this Feature space without touching the existing code and introducing new bugs.
  • When to Use the Prototype Pattern

  • It must be noted that while the Prototype Design Pattern is a very useful and mobile creational pattern, it is not always feasible. Its suitability comes from the fact that you can create objects in certain cases where it is impossible or unproductive to use various approaches like constructors or factory methods. Below are the key situations where the Prototype Pattern is an ideal choice:
  • When Object Creation Is Expensive The most utility of the Prototype Pattern is seen when objects are to be created because it can be a time-consuming process. For instance, if a certain object instantiation entails a significant amount of computational work, large data structures, or connections to other systems, such as databases or servers, then the constant creation of such objects will become very expensive. However, the Prototype Pattern makes it possible to instantiate the first instance and then change it before copying it to produce other objects. This approach dramatically brings down overhead particularly in the contexts that require frequent performance. For instance, when working in a 3D graphs application like Maya, making many instances of a complicated object such as a car or building from fresh needs a huge amount of resources. It is effective and consistent to clone a preconfigured prototype.
  • When Object Initialization Is Complex It may take several steps before an object can be initialized, pre-conditions, or readiness of the structures that the object is dependent on among others. When we put such logic right in a constructor it is only going to make the code messy and also hard to manage. The Prototype Pattern is useful here because it hides the complex initialization code into a prototype. After the prototype has been configured it can be used as a pattern from which new instances of the class can be made. This separation of concerns makes the code cleaner and easier to debug while at the same time guaranteeing that every clone originates from the same template object.
  • When You Need to Avoid Subclassing When subclassing might generate too many classes or over-complication of some aspects of the application the Prototype Pattern is a graceful solution. Instead of creating many subclasses to describe different kinds of objects, one prototype can be generated and can be copied and altered on the fly. This approach is better for the overall system maintainability since it minimizes the need for a large class hierarchy. For example, in a drawing application, rather than creating a class of each type of shape like circle, square, triangle, etc., a single Shape prototype can be created and copied to make circles, squares, and triangles, respectively.
  • When the System Requires Dynamic Object Creation The Prototype Pattern is especially useful in systems where the class of objects to be created is unknown until the run time. In such cases, therefore, a registry of prototypes will enable the system to create objects that are dynamically created. These deposits facilitate the call for prototypes to be cloned without hardcoding of the object creation logic. For instance, in a plugin-based design, the application may require or may have to instantiate objects belonging to a plugin that is dynamically loaded. The Prototype Pattern aids this by enabling every plugin to register a prototype that the system can then instantiate as many times as possible.
  • When objects have a hierarchical structure The latter, in turn, are objects with hierarchical structures or objects consisting of nested components, and they will be most beneficial for using the Prototype Pattern. When cloning such objects, all the relations are replicated, and the structure of the compound elements is preserved. Such a capability is especially useful in spheres like data visualization, scene graphs, or organizational charts, for example. For example, a clone operation, as in the case of, say, the root node in a tree data structure, can be made to use a prototype to clone the child nodes as well. This makes it easier to copy other complications like their hierarchies and, at the same time, avoid distortion of these hierarchies.
  • When You Need to Create Many Similar Objects The obvious scenario to use the Prototype Pattern is when a large number of similar objects have to be created. As an alternative to constructing each object directly, the system can just create a fresh copy of an object instance that can be set up as a template or role model so that the objects have the same properties. For instance, in a document processing system, the documents that are frequently or regularly generated can be pre-stored as prototypes of invoices, reports, letters, etc. In the case of a new document required, the system replicates the necessary template and then makes certain modifications, say, entering a recipient's information or inserting text.
  • The most utility of the Prototype Pattern is seen when objects are to be created because it can be a time-consuming process. For instance, if a certain object instantiation entails a significant amount of computational work, large data structures, or connections to other systems, such as databases or servers, then the constant creation of such objects will become very expensive. However, the Prototype Pattern makes it possible to instantiate the first instance and then change it before copying it to produce other objects.
  • This approach dramatically brings down overhead particularly in the contexts that require frequent performance. For instance, when working in a 3D graphs application like Maya, making many instances of a complicated object such as a car or building from fresh needs a huge amount of resources. It is effective and consistent to clone a preconfigured prototype.
  • It may take several steps before an object can be initialized, pre-conditions, or readiness of the structures that the object is dependent on among others. When we put such logic right in a constructor it is only going to make the code messy and also hard to manage.
  • The Prototype Pattern is useful here because it hides the complex initialization code into a prototype. After the prototype has been configured it can be used as a pattern from which new instances of the class can be made. This separation of concerns makes the code cleaner and easier to debug while at the same time guaranteeing that every clone originates from the same template object.
  • When subclassing might generate too many classes or over-complication of some aspects of the application the Prototype Pattern is a graceful solution. Instead of creating many subclasses to describe different kinds of objects, one prototype can be generated and can be copied and altered on the fly.
  • This approach is better for the overall system maintainability since it minimizes the need for a large class hierarchy. For example, in a drawing application, rather than creating a class of each type of shape like circle, square, triangle, etc., a single Shape prototype can be created and copied to make circles, squares, and triangles, respectively.
  • The Prototype Pattern is especially useful in systems where the class of objects to be created is unknown until the run time. In such cases, therefore, a registry of prototypes will enable the system to create objects that are dynamically created. These deposits facilitate the call for prototypes to be cloned without hardcoding of the object creation logic.
  • For instance, in a plugin-based design, the application may require or may have to instantiate objects belonging to a plugin that is dynamically loaded. The Prototype Pattern aids this by enabling every plugin to register a prototype that the system can then instantiate as many times as possible.
  • The latter, in turn, are objects with hierarchical structures or objects consisting of nested components, and they will be most beneficial for using the Prototype Pattern. When cloning such objects, all the relations are replicated, and the structure of the compound elements is preserved. Such a capability is especially useful in spheres like data visualization, scene graphs, or organizational charts, for example.
  • For example, a clone operation, as in the case of, say, the root node in a tree data structure, can be made to use a prototype to clone the child nodes as well. This makes it easier to copy other complications like their hierarchies and, at the same time, avoid distortion of these hierarchies.
  • The obvious scenario to use the Prototype Pattern is when a large number of similar objects have to be created. As an alternative to constructing each object directly, the system can just create a fresh copy of an object instance that can be set up as a template or role model so that the objects have the same properties.
  • For instance, in a document processing system, the documents that are frequently or regularly generated can be pre-stored as prototypes of invoices, reports, letters, etc. In the case of a new document required, the system replicates the necessary template and then makes certain modifications, say, entering a recipient's information or inserting text.

Code:

Example

#include <iostream>
#include <unordered_map>
#include <memory>
// Prototype Interface
class Shape {
public:
    virtual ~Shape() = default;
    virtual std::shared_ptr<Shape> clone() const = 0; // Prototype method
    virtual void draw() const = 0;                   // Display the shape
};
// Concrete Prototype: Circle
class Circle : public Shape {
    double radius;
public:
    Circle(double r = 0.0) : radius(r) {}
    void setRadius(double r) { radius = r; }
    std::shared_ptr<Shape> clone() const override {
        return std::make_shared<Circle>(*this); // Deep copy
    }
    void draw() const override {
        std::cout << "Circle with radius: " << radius << std::endl;
    }
};
// Concrete Prototype: Rectangle
class Rectangle : public Shape {
    double width, height;
public:
    Rectangle(double w = 0.0, double h = 0.0) : width(w), height(h) {}
    void setDimensions(double w, double h) {
        width = w;
        height = h;
    }
    std::shared_ptr<Shape> clone() const override {
        return std::make_shared<Rectangle>(*this); // Deep copy
    }
    void draw() const override {
        std::cout << "Rectangle with width: " << width << ", height: " << height << std::endl;
    }
};
// Prototype Registry
class ShapeRegistry {
    std::unordered_map<std::string, std::shared_ptr<Shape>> prototypes;
public:
    void registerPrototype(const std::string &key, std::shared_ptr<Shape> prototype) {
        prototypes[key] = std::move(prototype);
    }
    std::shared_ptr<Shape> createShape(const std::string &key) const {
        if (prototypes.find(key) != prototypes.end()) {
            return prototypes.at(key)->clone();
        }
        throw std::runtime_error("Prototype not found!");
    }
};
// Main Function
int main() {
    ShapeRegistry registry;
    // Register prototypes
    registry.registerPrototype("circle", std::make_shared<Circle>(5.0));
    registry.registerPrototype("rectangle", std::make_shared<Rectangle>(3.0, 4.0));
    // Clone and modify shapes
    auto clonedCircle = registry.createShape("circle");
    auto clonedRectangle = registry.createShape("rectangle");
    // Customize the cloned instances
    clonedCircle->draw();
    clonedRectangle->draw();
    auto anotherCircle = clonedCircle->clone();
    auto anotherRectangle = clonedRectangle->clone();
    // Modifying cloned shapes
    std::dynamic_pointer_cast<Circle>(anotherCircle)->setRadius(10.0);
    std::dynamic_pointer_cast<Rectangle>(anotherRectangle)->setDimensions(6.0, 8.0);
    std::cout << "\nModified Shapes:\n";
    anotherCircle->draw();
    anotherRectangle->draw();
    return 0;
}

Output:

Output

Circle with radius: 5
Rectangle with width: 3, height: 4
Modified Shapes:
Circle with radius: 10
Rectangle with width: 6, height: 8

Conclusion:

In summary, the Prototype Design Pattern is a robust creational design technique that leverages prototypes to create fully initialized objects. This pattern is particularly beneficial in scenarios where object creation is resource-intensive and involves intricate setup or configuration during runtime. Implementation of this pattern enhances object performance, reduces code duplication, and ensures consistent behavior among different instances as they are duplicates of an original object rather than fresh creations.

The next benefit of the Prototype Pattern lies in the simplicity of customization embedded in the method explained earlier. This implies that duplicated objects are distinct and can be tailored without impacting the original prototype; this feature makes duplication ideal for creating variations of a foundational object. The precision of a prototype repository adds even more adaptability - objects can be produced dynamically, and the system remains adaptable.

This design pattern aligns effectively with the concepts of modularity and abstraction, contributing to the enhancement of code excellence and aligning with the Open/Closed Principle. Overall, the Prototype Pattern showcases strong performance in demanding scenarios like graphical applications, simulations, and hierarchical data structures. It may not be the optimal choice for every scenario, especially in cases involving low-cost object creation tasks.

Overall, the Prototype Pattern serves as a robust mechanism that facilitates the development of systems that can be efficiently built, scaled effectively, maintained effortlessly, and streamline the object creation process with minimal additional burden.

Input Required

This code uses input(). Please provide values below:

Logic Practice
Install Logic Practice
Add to home screen for a faster app-like experience