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