Object Pool Pattern and Prototype Pattern

Object Pool Pattern

Object Pool Pattern is a type of Creational Design Pattern that helps to improve the performance by reusing objects which have already been created instead of creating new objects each time they are needed. It works by maintaining a pool of objects, and clients can request an object from the pool when they need it, and then return it to the pool once they are done using it.

This pattern is useful in situations where creating and destroying objects is expensive, such as when objects require a lot of memory or take a long time to initialize. By reusing objects from the pool, the overhead of creating and destroying objects is reduced, which can lead to improved performance and reduced resource consumption.

The Object Pool Pattern can be implemented in a variety of ways, but the basic idea is to maintain a pool of objects and manage access to them. When a client requests an object, it is retrieved from the pool and given to the client. When the client is done with the object, it is returned to the pool so that it can be reused later.

Object Pool Pattern should be considered using

  • When creating objects is expensive, such as when a database connection, thread, or network connection is required.
  • When the number of objects required is potentially large and variable, but it’s not always practical to create and maintain that many instances.
  • When objects can be reused among multiple clients, reducing the overhead of creating and destroying new objects for each client.
  • When there is a limited amount of a particular resource that needs to be shared among many clients, such as in a game engine where there is a limited number of particles, sounds, or animations that can be active at any one time.

Let us try to understand it with an example. Let’s say you have a web application that needs to generate PDFs for users on demand. Generating a PDF can be a computationally expensive task, so you want to optimize your application’s performance by reusing PDF generation objects instead of creating new ones every time a user requests a PDF.

You can use the Object Pool pattern to manage a pool of pre-initialized PDF generation objects. When a user requests a PDF, your application can take an available PDF generation object from the pool, use it to generate the PDF, and then return the object to the pool for reuse. If all objects in the pool are currently in use, your application can either wait for an object to become available or create a new one (assuming a maximum limit for the pool has not been reached).

This approach saves time and resources, because it eliminates the need to constantly create new PDF generation objects, which can be computationally expensive. It also helps manage the limited resources of the system by controlling the maximum number of objects that can be in use at any given time.


Prototype Pattern

The Prototype Pattern is a creational design pattern that involves creating objects based on existing objects. In other words, it allows you to create new objects by copying existing ones. Instead of creating an object from scratch, you can create a new object by cloning an existing one.

This pattern is useful when you need to create many similar objects that differ only in their initial state. Instead of creating each object from scratch, you can create one object with the desired initial state and then clone it to create new objects.

The Prototype Pattern can also be used to avoid the complexity of creating new objects by providing a pre-built set of objects that can be cloned when needed.

Lets understand it in deep using an example by consider a scenario where we have to create multiple instances of a complex and resource-intensive object in an embedded system, such as a complex data structure or a network connection. Creating a new instance every time we need it can be time-consuming and resource-intensive. In such cases, we can use the Prototype pattern to clone the existing instance instead of creating a new one from scratch.

For instance, consider a real-time system that uses a wireless network to communicate with remote devices. The system uses a complex data structure to store and process the network packets received from these devices. Creating a new instance of this data structure every time a new packet is received can be expensive and can affect the real-time performance of the system.

To overcome this, we can use the Prototype pattern to create a single instance of the data structure at the startup of the system and then clone it whenever we need a new instance. This way, we can avoid the overhead of creating a new instance from scratch every time a new packet is received.

Prototype pattern should be used in below cases:

  • When creating an object is costly in terms of time, resources or other factors, and you want to avoid creating new objects from scratch.
  • When you want to separate the construction of complex objects from their representation, allowing for easier and more flexible creation of new objects.
  • When you have a class hierarchy that is difficult to change, but you still need to add new types of objects to it.
  • When you want to reduce the number of subclasses that are needed to create new objects with varying configurations.

In the C++ implementation of this example, we can define a prototype object for the data structure and provide a method to clone it. Whenever we need a new instance of the data structure, we can use the clone method to create a copy of the prototype object.

#include <iostream>
#include <string>

// Complex data structure
class PacketData {
public:
    PacketData() {}

    PacketData(const PacketData& other) {
        // Copy constructor
        // Perform deep copy of the data
        this->data = other.data;
    }

    void setData(std::string data) {
        this->data = data;
    }

    std::string getData() {
        return this->data;
    }

private:
    std::string data;
};

// Prototype object
class PacketDataPrototype {
public:
    virtual PacketDataPrototype* clone() = 0;

    virtual PacketData* getData() {
        return &this->data;
    }

protected:
    PacketData data;
};

// Concrete prototype object
class WirelessPacketDataPrototype : public PacketDataPrototype {
public:
    WirelessPacketDataPrototype() {
        // Initialize the data structure with default values
        this->data.setData("Default wireless packet data");
    }

    virtual PacketDataPrototype* clone() {
        // Create a new instance and perform a deep copy of the data
        return new WirelessPacketDataPrototype(*this);
    }
};

// Client code
int main() {
    // Create a prototype object
    PacketDataPrototype* prototype = new WirelessPacketDataPrototype();

    // Clone the prototype to create a new instance
    PacketDataPrototype* instance1 = prototype->clone();

    // Modify the data of the new instance
    PacketData* data1 = instance1->getData();
    data1->setData("Wireless packet data 1");

    // Clone the prototype again to create a new instance
    PacketDataPrototype* instance2 = prototype->clone();

    // Modify the data of the new instance
    PacketData* data2 = instance2->getData();
    data2->setData("Wireless packet data 2");

    // Print the data of the instances
    std::cout << "Instance 1 data: " << data1->getData() << std::endl;
    std::cout << "Instance 2 data: " << data2->getData() << std::endl;

    // Cleanup
    delete prototype;
    delete instance1;
    delete instance2;

    return 0;
}

Reference

Related posts