Design patterns: Object pool

in #design-patterns7 years ago

goatpool.png

In this article, I will be talking about the design pattern, which is called Object pool, I wanted to describe it now because it is quite similar to the Flyweight pattern.

Intent

  1. Keeping initialized objects in the pool ready to use.
  2. Returning the object on which the client performed operations back to the pool.
  3. Not creating costly classes repeatedly to create, at once created expensive objects are returned back to the pool.

Problem

The object pool pattern as like Flyweight is related to caching, on the principle just that once created object by the client, it is returned back to the pool, when the client will need it again, the object will be retrieved from the pool, but it will not be created again from the beginning because it has already been created, you can also set the number of objects that can be stored in the pool of objects.

Properly used pool can significantly increase the performance of the application. The pool of objects is particularly useful, for example, in connections with the database or in threads, because the object pool class is singleton, i.e. it is protected from accessing from multiple threads in an instant.

However, it also works the other way, incorrect use of the Object pool eg in creating objects that only occupy memory, but do not point to other resources, can significantly reduce performance.

Use when:

  1. You must create objects that are expensive to create
  2. The frequency of creating further objects is also high.
  3. The number of objects in use is small.

Discussion

It must be remembered that when we return an object to Object pool, its state must be reset, if we return the object on which the client performed some operations, then after another need to use it by the client, he can perform actions not as we would like. The object pool which doesn’t reset the states of returned objects is an anti-pattern. The object reset mechanism must be implemented in the Object pool, the client should not have to reset objects because it is not the client that is responsible for resetting the objects, only the pool.

Structure

The UML diagram clearly shows that Object pool class is the singleton, because there is a getInstance() method that creates an object in the class. The method acquireReusable(), that is responsible for creating the object and saving it to the pool as a used object. The method releaseReusable(), which is responsible for releasing the object from the client, and inserting it into the objects available for use in Pula, but already unused, in practical examples this will be better explained.

And the last method that is responsible for the maximum number of available objects in the pool.

And of course, the client that calls the getInstance() method to create an Object Pool instance. Then, using the acquireReusable() method, it creates an object and writes it to the pool.

objectpoolstructure.png

Example

Below is an example of the implementation of the Object pool design pattern

namespace ObjectPool
{
    class ObjectPool<T> where T : new()
    {
        private static List<T> _available = new List<T>();
        private List<T> _inUse = new List<T>();
 
        private int counter = 0;
        private int MAXTotalObjects;
 
        private static ObjectPool<T> instance = null;
 
        public static ObjectPool<T> GetInstance()
        {
            if (instance == null)
            {
                instance = new ObjectPool<T>();
            }
            else
            {
                Console.WriteLine("This is singleton!");
            }
            return instance;
        }
 
        public T acquireReusable()
        {
            if (_available.Count != 0 && _available.Count < 10)
            {
                T item = _available[0];
                _inUse.Add(item);
                _available.RemoveAt(0);
                counter--;
                return item;
            }
            else
            {
                T obj = new T();
                _inUse.Add(obj);
                return obj;
            }
        }
 
        public void ReleaseReusable(T item)
        {
            if (counter <= MAXTotalObjects)
            {
                _available.Add(item);
                counter++;
                _inUse.Remove(item);
            }
            else
            {
                Console.WriteLine("To much object in pool!");
            }
        }
 
        public void SetMaxPoolSize(int settingPoolSize)
        {
            MAXTotalObjects = settingPoolSize;
        }
    }
 
    class PooledObject
    {
        DateTime _createdAt = DateTime.Now;
 
        public DateTime CreatedAt
        {
            get { return _createdAt; }
        }
 
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            ObjectPool<PooledObject> objPool = ObjectPool<PooledObject>.GetInstance();
            objPool.SetMaxPoolSize(10);
            PooledObject obj = objPool.acquireReusable();
            objPool.ReleaseReusable(obj);
            Console.WriteLine(obj.CreatedAt);
            Console.ReadKey();
        }
    }
}

And this is more or less the pattern implementation scheme of the Object pool, you can see that the ObjectPool class is a generic class, because you will not always know what classes you want to save in the pool of objects, we save them in the list dividing them into available objects and objects currently used , the GetInstance() method creates an object of the ObjectPool class, and prevents the creation of two object pools because this class is singleton.

So we create an instance of objectPool in the Main function to which we write the sample class PooledObject. We set the maximum of objects that can be accessed in the ObjectPool class on 10.

Next, we call the acquireReusable() method, which will first check if such an object is available and if there are less those objects than 10 if so, it saves the first available object from the list to the generic variable item , and adds to the objects used, and delete the object available, because it is not available, it in use and we reduce the object counter because we count available objects, unused objects.

And if any of the conditions do not match, we create an instance of the PooledObject class and save it to the list of used objects. And the last method ReleaseReusable which we pass down an object that we do not want to use anymore, in this method we check whether the counter exceeded the maximum value if not, transferred object remove from used and we save to available and increase counters, because we added another object to available objects.

And of course, if we’d creating something bigger, we should reset the state of the returned object in this case, we do not have to, but remember about it.

Result:

poolobjectfirst.png

And ObjectPool class have been correct created?🤔

billgatesenglish.jpg

Well, not exactly because we have not secured it in a multithread, secure multithread class looks like this:

namespace AsynchronousObjectPool
{
    class ObjectPool<T> where T : new()
    {
        private static List<T> _available = new List<T>();
        private List<T> _inUse = new List<T>();
 
        private int counter = 0;
        private int MAXTotalObjects;
 
        private static ObjectPool<T> instance = null;
 
        public static ObjectPool<T> GetInstance()
        {
            lock (_available)
            {
                if (instance == null)
                {
                    instance = new ObjectPool<T>();
                }
                else
                {
                    Console.WriteLine("This is singleton!");
                }
                return instance;
            }
        }
 
        public T acquireReusable()
        {
            lock (_available)
            {
                if (_available.Count != 0 && _available.Count < 10)
                {
                    T item = _available[0];
                    _inUse.Add(item);
                    _available.RemoveAt(0);
                    counter--;
                    return item;
                }
                else
                {
                    T obj = new T();
                    _inUse.Add(obj);
                    return obj;
                }
            }
        }
 
        public void ReleaseReusable(T item)
        {
            lock (_available)
            {
                if (counter < MAXTotalObjects)
                {
                    _available.Add(item);
                    counter++;
                    _inUse.Remove(item);
                }
                else
                {
                    Console.WriteLine("To much object in pool!");
                }
            }
        }
 
        public void SetMaxPoolSize(int settingPoolSize)
        {
            MAXTotalObjects = settingPoolSize;
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            Task task1 = Task.Run(() =>
            {
                ObjectPool<PooledObject> objPool = ObjectPool<PooledObject>.GetInstance();
                objPool.SetMaxPoolSize(10);
                PooledObject obj = objPool.acquireReusable();
                objPool.ReleaseReusable(obj);
                Console.WriteLine(obj.CreatedAt);
                Console.WriteLine("This is the first thread");
            });
 
            Task task2 = Task.Run(() =>
            {
                ObjectPool<PooledObject> objPool = ObjectPool<PooledObject>.GetInstance();
                objPool.SetMaxPoolSize(10);
                PooledObject obj = objPool.acquireReusable();
                objPool.ReleaseReusable(obj);
                Console.WriteLine(obj.CreatedAt);
                Console.WriteLine("This is the second thread");
            });
 
            Console.ReadKey();
        }
    }
 
    class PooledObject
    {
        DateTime _createdAt = DateTime.Now;
 
        public DateTime CreatedAt
        {
            get { return _createdAt; }
        }
 
    }
}

We protect methods of the ObjectPool class with a the lock word about which I mentioned earlier in the concurrency article, we call an example from two clients, i.e. we have a secured class, we will display a message at the beginning “this is singleton”, so we will have only one instance anyway ObjectPool class, due to the nature of asynchrony, the result may look like this:

asyn1.png

Or:

asyn2.png

An example from of life taken

Warehouse

We will make an example on a warehouse basis, when the owner hires an employee, takes the appropriate equipment from the warehouse and gives it to the employee and when the employee is released, the equipment is returned to the warehouse, this is shown well in the picture below.

objectpoolwarehouse.png

Now let’s see how it looks in the code:

namespace Warehouse
{
    class Warehouse<T> where T : new()
    {
        private static List<T> _availableEquipment = new List<T>();
        private List<T> _inUseEquipment = new List<T>();
 
        public int counter = 0;
        private int MAXTotalObjects;
 
        private static Warehouse<T> instance = null;
 
        public static Warehouse<T> GetInstance()
        {
            if (instance == null)
            {
                instance = new Warehouse<T>();
            }
            else
            {
                Console.WriteLine("This is singleton!");
            }
            return instance;
        }
 
        public T GiveEquipmentWorker()
        {
            if (_availableEquipment.Count != 0 && _availableEquipment.Count < 10)
            {
                T item = _availableEquipment[0];
                _inUseEquipment.Add(item);
                _availableEquipment.RemoveAt(0);
                counter--;
                return item;
            }
            else
            {
                T obj = new T();
                _inUseEquipment.Add(obj);
                return obj;
            }
        }
 
        public void ReturnEquipmentToWarehouse(T item)
        {
            if (counter <= MAXTotalObjects)
            {
                _availableEquipment.Add(item);
                counter++;
                _inUseEquipment.Remove(item);
            }
            else
            {
                Console.WriteLine("To much object in pool!");
            }
        }
 
        public void SetMaxPoolSize(int settingPoolSize)
        {
            MAXTotalObjects = settingPoolSize;
        }
    }
}

The logic of the object pool class remains the same as in the previous examples, only the names are different. We’ve just added two new Store and Workspace classes.

namespace Warehouse
{
    interface IWorkSpace
    {
        void IfWorkerWasEmployed();
        void IfWorkerWasFired();
    }
 
    class WorkSpace : IWorkSpace
    {
        public void IfWorkerWasEmployed()
        {
            Console.WriteLine("Worker work in shop");
        }
 
        public void IfWorkerWasFired()
        {
            Console.WriteLine("The employee was released");
        }
    }
 
    class Store
    {
        public int workers;
        public Warehouse<WorkSpace> objPool;
 
        public void employWorker()
        {
            workers++;
        }
 
        public void OrderEquipment()
        {
            objPool = Warehouse<WorkSpace>.GetInstance();
            objPool.SetMaxPoolSize(1);
        }
 
        public int employees()
        {
            return workers;
        }
 
        public void FireAnEmployee(WorkSpace obj)
        {
            workers--;
            objPool.ReturnEquipmentToWarehouse(obj);
        }
 
        public void CheckThatWorkerWasFired(WorkSpace obj, bool ifEmployeeWorker)
        {
            if (ifEmployeeWorker == true)
            {
                obj.IfWorkerWasEmployed();
            }
            else if (ifEmployeeWorker == false)
            {
                obj.IfWorkerWasFired();
            }
        }
    }
}

The Store class uses the object pool class, that is, the Warehouse class, which has functions that return the object to the object pool, set the maximum number of objects in the pool, etc. and the Workspace class only displays the appropriate messages.

And calling it all in the client.

    class Program
    {
        static void Main(string[] args)
        {
            Store store = new Store();
            store.employWorker();
            store.OrderEquipment();
            WorkSpace obj = store.objPool.GiveEquipmentWorker();
 
            Console.WriteLine("The value of the counter in the Warehouse class: " + store.objPool.counter);
 
            store.CheckThatWorkerWasFired(obj,true);
 
            Console.WriteLine(store.workers);
 
            store.FireAnEmployee(obj);
 
            Console.WriteLine("The value of the counter in the Warehouse class: "+store.objPool.counter);
 
            store.CheckThatWorkerWasFired(obj, false);
 
            Console.WriteLine(store.workers);
 
            Console.ReadKey();
        }
    }

So generally speaking, the procedure similar as before, only the calls ObjectPool class in this case Warehouse are mostly in the Store class, anyway there is not much difference, this example does not have to be secured in a multi-threaded way because we assume that there is only one warehouse, and the store too there is only one but if you wanted to do it, you know how. I’ve already given an example of how to do it. Analyze this example calmly.

Result:

wynik10.png

Flyweight and Object Pool differences

  1. The main difference is that Flyweight resources are immutable and the Object pool are mutable.
  2. In the Object pool, at any given moment the object can be accessed only for one client, in Flyweight many clients can simultaneously use the same object.

Summary

And that’s all about the Object pool and, in general, about constructional patterns in the next article is about the Adapter design pattern. As I have recently many activities, so, an Interpreter design pattern will be an entry for two weeks, unfortunately life 😐

Link to github with the whole code from this article: https://github.com/Slaw145/ObjectPool

This content also you can find on my blog http://devman.pl/programtech/design-patterns-object-pool/

If you recognise it as useful, share it with others so that others can also use it.

Leave upvote and follow and wait for next articles :) .

And NECESSERILY join the DevmanCommunity community on fb, part of the community is in one place 🙂

– site on fb: Devman.pl-Sławomir Kowalski

– group on fb: DevmanCommunity

Ask, comment underneath at the end of the post, share it, rate it, whatever you want🙂.