Пул об'єктів (шаблон проєктування)

Пул об'єктів (англ. Object pool) — твірний шаблон проєктування, набір ініціалізованих і готових до використання об'єктів. Коли системі потрібно об'єкт, він не створюється, а береться з пулу. Коли об'єкт більше не потрібен, він не знищується, а повертається в пул.

Призначення ред.

Пул об'єктів призначений для зберігання готових до використання об'єктів. Коли система потребує новий об'єкт, він запрошується з пула, нехтуючи процесом породження. А після використання повертається назад в пул замість знищення.

Умови використання ред.

Шаблон використовується для підвищення продуктивності, якщо:

  • об'єкти часто створюються і знищуються;
  • система має обмежену кількість типів об'єктів, що зберігаються в Пулі.
  • створення чи знищення об'єкта є дуже затратною операцією.

Пул об'єктів може працювати як з інтерфейсами, так і з конкретними реалізаціями. Все залежить від архітектури системи, що розробляється та розв'язуваних задач.

Пул та інші шаблони ред.

Можна зустріти сумісне використання Пула об'єктів та інших шаблонів проєктування. Наприклад, для створення об'єктів в конкретному стані можна використати Прототип. А за допомогою Одинака — створити єдиний екземпляр Пула в системі.

Поганою практикою вважається приховування Пула за іншими шаблонами проєктування. Розробник, який використовує такий підхід, не очікує вимоги повернення об'єктів, наприклад, Фабричного методу. А без повернення об'єктів сам Пул стає непотрібним. В такому випадку, правильним рішенням буде відділити реалізацію класів, що створюють об'єкти.

Особливості використання ред.

Пул нічого не знає про реалізацію об'єктів, які зберігаються. Тому вважається, що повернений об'єкт знаходиться в невизначеному стані. Для подальшого використання його необхідно перевести в початковий стан (скидання). Наявність об'єктів в невизначеному стані перетворює Пул в «об'єктну клоаку» (object cesspool). Повторне використання може стати причиною витоку конфіденційної інформації. Тому обов'язково необхідно зчищати поля з секретними даними при скиданні, а самі дані — знищувати. Можлива ситуація, коли в Пулі не залишиться вільних об'єктів. В такому випадку, реакція на запит може бути наступною:

  • збільшення розміру пула;
  • відмова у видачі об'єкта;
  • становлення в чергу і очікування звільнення об'єкта.

Реалізація ред.

Реалізація на C# ред.

Роздивимось варіант роботи з Пулом без обмежень в його розмірі. В такому випадку в нього можна помістити стільки об'єктів, скільки підтримує використовуємий контейнер. Якщо ж при запиті не виявиться вільного об'єкта, то буде створений новий.

В загальному випадку Пул не повинен нічого знати про спосіб створення, реалізацію і функції об'єктів, що зберігаються. Для нього важливим є тільки мати можливість дати команду скидання стану.

Створимо два інтерфейси. Перший — для скидання стану, його повинні підтримувати самі об'кти:

/// <summary> The poolable object interface </summary>

public interface IPoolable
{
    /// <summary>Resets the object's state.</summary>
    void ResetState();
}

Другий — для створення об'єктів, тому що Пул не знає як саме це робити. Тут може бути як варіант з одним ключовим словом new, так і використання будь-якого шаблона проєктування.

/// <summary> The pool object creator interface. </summary>
/// <typeparam name="T">Type of the objects to create.</typeparam>
public interface IPoolObjectCreator<T>
{
    /// <summary>Creates new object for a pool.</summary>
    /// <returns>The object.</returns>
    T Create();
}

Одразу реалізуємо даний інтерфейс у вигляді generic класу для створення екземплярів за допомогою конструктора без параметрів:

public class DefaultObjectCreator<T> : IPoolObjectCreator<T> where T : class, new()
{
    T IPoolObjectCreator<T>.Create()
    {
        return new T();
    }
}

Перейдемо до створення Пула. Як контейнер об'єктів використовуємо клас ConcurrentBag, реалізований в .NET4. Оскільки розмір Пула не фіксований, то його метод GetObject() завжди повертає об'єкт. Метод ReturnObject() переміщує об'єкт назад у контейнер. При цьому відбувається скидання його стану. Крім того, змінній, що тримала посилання на нього, присвоюється значення null. Властивість Count показує скільки об'єктів в даний момент знаходиться в пулі.

Повний код класу, що реалізує шаблон Пул об'єктів:

public class ObjectPool<T> where T : class, IPoolable

{
    /// <summary>Object container. ConcurrentBag is tread-safe class.</summary>
    private readonly ConcurrentBag<T> _container = new ConcurrentBag<T>();
    /// <summary>Object creator interface.</summary>
    private readonly IPoolObjectCreator<T> _objectCreator;
    /// <summary>Total instances.</summary>
    public int Count { get { return this._container.Count; } }
    /// <summary>
    /// Initializes a new instance of the <see cref="T:ObjectPool"/> class.
    /// </summary>
    /// <param name="creator">Interface of the object creator. It can't be null.</param>
    public ObjectPool(IPoolObjectCreator<T> creator)
    {
        if (creator == null) {
            throw new ArgumentNullException("creator can't be null");
        }
        this._objectCreator = creator;
    }
    /// <summary>Gets an object from the pool.</summary>
    /// <returns>An object.</returns>
    public T GetObject()
    {
        T obj;
        if (this._container.TryTake(out obj)) {
            return obj;
        }
        return this._objectCreator.Create();
    }
    /// <summary>Returns the specified object to the pool.</summary>
    /// <param name="obj">The object to return.</param>
    public void ReturnObject (ref T obj)
    {
        obj.ResetState();
        this._container.Add(obj);
        obj = null;
    }
}

Джерела ред.