What is Object Pool Design Pattern Simplified

In this article, let's understand what is an Object Pool, why is it necessary to pool objects and how to implement a simple object pool with an example in .NET

While developing applications, resource management is one of the basic performance parameters we need to look at.

Since memory can be a limited resource, creating and destroying objects over time in an application can result in a performance hit. Things get even worse when we’re dealing with heavy objects for which the initialization cost is high.

What is an Object Pool?

Object Pool is one of the 23 GoF (Gang of Four) design patterns which address frequently occurring design problems during an application development. It is a Creational Pattern which offers solutions for better object creations and management.

How Object Pool works?

Object Pool pattern addresses the above said frequent object creation issue by using a set of initialized objects ready in an “object pool” and lets the clients which require that “pull” an object from the “pool” and release it back to the pool once its done with the object.

This reduces the need for creating objects when required and hence can avoid the initialization cost for such scenarios.

Generally, Object Pools maintain a collection of the available objects along with a collection of objects in use. Whenever an object is requested, the Pool checks for an available object and if available returns the object and places its reference in the used counter.

Once that object is released by the client it drops the reference of that object from its used counter and places the object back into available collection.

However, when the pool is out of available objects to serve for a requesting client, it employs different strategies to handle the scenario, such as:

  • Throws an error that there’s no available object
  • Expands the Pool size – creates a new object and returns to the client
  • Blocks the client until an object is released by another client

An Object Pool is completely responsible for “resetting” the state of the object when it is returned by the client before marking it available for other clients to use it.

An Object Pool is implemented as a Singleton class

Advantages of using an Object Pool

  • Significantly increases the application performance since the initialization cost is now eliminated
  • Provides a neat mechanism of reusing objects among the requesting clients, while it being responsible for the object management
  • Helpful in scenarios where objects are created frequently but are used for a shorter lifespan
  • Advantageous in cases of objects which have a variable creation time such as database connections, socket connections, threads and so on

Things to keep in Mind while using Object Pool

While using an Object Pool is quite useful, developers must keep in mind that:

  • Implementing an Object Pool manually means that the Pool is now responsible for keeping track of the objects which are currently live and which are to be disposed.
  • In modern programming languages where the object allocation happens quite faster, such classic object pool designs might consume more resources.
  • Object Pools must be created and used only in scenarios where frequent object creations happen; in cases where the pool maintains a set of objects which are not frequently accessed can increase the occupied memory, which can become an issue instead of advantage.
  • In cases of simple object pooling, where the objects hold only memory and not any external resources (such as sockets, database connections and so on), this may not be an efficient solution.

How to implement an Object Pool with .NET Core

In a simple implementation of an Object Pool, we have an interface which declares two methods for any of its implementation: a GetObject() method which returns an object from the pool, and a Release() method which passes the object which is to be reused.

public interface IObjectPool<T> 
{
    T GetObject();
    void Release(T o);
}

The ReaderObjectPool implements this interface for maintaining a “pool” of reusable Reader instances. The GetObject() method employs an “elastic pool” approach, where the size of the pool increases when the pool runs out of Reader instances.

public class ReaderObjectPool : IObjectPool<Reader>
{
    private List<Reader> _available, _used;

    public ReaderObjectPool()
    {
        _available = new List<Reader>();
        _used = new List<Reader>();
    }

    public Reader GetObject()
    {
        lock (_available)
        {
            if (_available.Count > 0)
            {
                Reader reader = _available[0];
                _available.RemoveAt(0);
                _used.Add(reader);
                return reader;
            }
            else
            {
                Reader reader = new Reader();
                _used.Add(reader);
                return reader;
            }
        }
    }

    public void Release(Reader reader)
    {
        lock (_available)
        {
          _used.Remove(reader);
          _available.Add(reader);
        }
    }
}

The Reader object is assumed to be a complex one, which takes a high initialization cost.

public class Reader
{
    public Guid Id { get; set; }
    public DateTime CreatedOn { get; set; }

    public Reader()
    {
        this.CreatedOn = DateTime.Now;
        this.Id = Guid.NewGuid();
    }

    public void DoSomeComplexProcess()
    {
        Console.WriteLine(
"This is Reader# {Id} Created on ${CreatedOn.ToString()}");
    }
}

In case of a DI container approach, the ObjectPool is marked as a Singleton so as to have a single global pool of resources across the application.

services.AddSingleton<IObjectPool<Reader>, ReaderObjectPool>();

or it is implemented separately as a Singleton:

public sealed class ReaderObjectPoolSingleton : IObjectPool<Reader>
{
    private static ReaderObjectPoolSingleton instance;

    // a private constructor
    private ReaderObjectPoolSingleton()
    {
    }
    
    public static ReaderObjectPoolSingleton GetInstance()
    {
        if (instance == null)
        {
            instance = new ReaderObjectPoolSingleton();
        }
        
        return instance;
    }

    public Reader GetObject()
    {
        // implementation
    }

    public void Release(Reader o)
    {
        // implementation
    }
}

At the client end, the ReaderObjectPool is used to get an object of type Reader and some operation happens. Finally once the need is over, the pulled instance is released back to the pool.

public class Client 
{
    private readonly IObjectPool<Reader> _pool;

    public Client(IObjectPool<Reader> readerPool)
    {
        _pool = readerPool;
    }

    public void DoProcess()
    {
        // Fetch a Reader from the Pool
        var reader = _pool.GetObject();

        // Do Something with the Reader fetched
        reader.DoSomeComplexProcess();

        // Release the Reader to the Pool 
        // so that others can use it
        _pool.Release(reader);
                
        return Ok();
    }
}

The output when this application is executed multiple times indicate that the same object from the pool has been reused for all consequent operations.

This is Reader# 9bea42ea-e383-4b6f-9977-374fab2e7838 Created on $18-07-2020 10:44:49 AM
This is Reader# 9bea42ea-e383-4b6f-9977-374fab2e7838 Created on $18-07-2020 10:44:49 AM

Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

Your email address will not be published. Required fields are marked *