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

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку

Пул об'єктів (англ. 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;
    }
}

Джерела[ред. | ред. код]