Patterns. Паттерн Singleton (Одинак). Огляд. Особливості застосування. Реалізація на C++




Паттерн Singleton (Одинак). Огляд. Особливості застосування. Реалізація на C++

Перед вивченням даної теми рекомендується ознайомитись з наступною темою:


Зміст


Пошук на інших ресурсах:

1. Паттерн Singleton (одинак). Загальні відомості. Обов’язки. Випадки застосування

Паттерн Singleton (одинак, одиночка) відноситься до породжуючих паттернів. Паттерн Singleton призначений для створення заданої кількості екземплярів (об’єктів) класу. Найчастіше паттерн Singleton використовується для створення гарантовано одного екземпляру класу.

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

Використання глобальних змінних не забезпечує обмеження на створення додаткових екземплярів класів. Глобальна змінна дає доступ до об’єкта, але не забороняє створювати декілька екземплярів класу. Тому, доцільно ці обмеження реалізувати в самому класі. Саме такий підхід пропонує паттерн Singleton.

У паттерні Singleton на клас накладаються наступні обов’язки:

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

Випадки застосування паттерну Singleton:

  • коли потрібно створити рівно один екземпляр класу;
  • коли потрібно створити не більше заданої кількості екземплярів класу;
  • коли потрібно розширити єдиний екземпляр класу похідними підкласами. У цьому випадку клієнт має можливість використовувати розширені засоби (операції, методи) підкласів без модифікації свого коду.

 

2. Структурна схема паттерну Singleton

Найчастіше, в літературі можна зустріти наступну структурну схему паттерну Singleton (рисунок 1).

Структурна схема паттерну Singleton

Рисунок 1. Структурна схема паттерну Singleton

На рисунку 1 відображається клас Singleton, який містить три методи:

  • Instance() – це є головний метод, в якому здійснюється створення екземпляру класу та контроль за кількістю створених екземплярів класу. Якщо кількість екземплярів класу не перевищує деяку максимальну (наприклад 1), то метод повертає екземпляр класу Singleton;
  • SingletonOperation() – деякий метод класу який виконує деяку роботу. Тут може бути декілька методів з будь-якими іменами (наприклад SingletonOperation1(), SingletonOperation2() і т.д.), які реалізують функціонал класу;
  • GetSingletonData() – метод класу, що повертає дані. Тут може бути реалізована різноманітна кількість методів в залежності від вимог, що встановлюються для класу.

Створення екземпляру класу в клієнтському коді здійснюється викликом функції Instance(). Створити екземпляр класу іншим способом не вдасться.

 

3. Приклад реалізації паттерну Singleton на C++. Створення одного екземпляру класу

У прикладі демонструється використання паттерну Singleton на однойменному класі. У класі Singleton реалізовані наступні члени:

  • внутрішня прихована (private) статична змінна _instance. Ця змінна зберігає покажчик на єдиний екземпляр класу. Як відомо, статичні змінні зберігаються у зовнішній пам’яті в одному екземплярі. Оскільки, ця змінна прихована, то доступ до неї здійснюється з методів класу;
  • внутрішня прихована змінна d. Ця змінна умовно визначає дані класу. За бажанням можна змінити дані класу на власні;
  • конструктор Singleton(). Конструктор ініціалізує внутрішню змінну d нульовим значенням. Конструктор поміщено в розділ protected для того, щоб неможливо було створити екземпляр класу з іншого коду в обхід методу Instance() а також дати можливість успадковувати даний клас. Якщо клієнт спробує інстанціювати клас Singleton безпосередньо, то виникне помилка на етапі компіляції;
  • статичний метод Instance() – повертає екземпляр класу Singleton шляхом виклику protected-конструктора класу. Покажчик на створений екземпляр зберігається у внутрішній змінній _instance;
  • загальнодоступні (public) методи Get(), Set() – реалізують доступ до даних (змінна d);
  • метод Print() – виводить значення внутрішніх даних (d) для контролю;
  • деструктор ~Singleton() – реалізує коректне звільнення пам’яті, виділеної під єдиний екземпляр класу.

Текст програми типу Console Application наступний:

#include <iostream>
using namespace std;

// Реалізація паттерну Singleton на C++.
// Створення не більше одного екземпляру класу Singleton
class Singleton
{
private:
  // Статична внутрішня змінна, яка зберігає одиночний екземпляр класу.
  // До цієї змінної є доступ з методів даного класу, тому що
  // вона оголошена як private. З методів інших класів доступу
  // до змінної немає.
  static Singleton* _instance;

  // Деякі дані класу, тут можуть бути будь-які дані
  int d;

protected:
  // Конструктор класу, оголошений як protected, для того щоб:
  // - неможливо було створити екземпляр класу безпосередньо оператором new;
  // - можна було успадковувати даний клас з інших класів.
  Singleton()
  {
    d = 0;
  }

public:
  // Статичний метод, який повертає екземпляр класу Singleton.
  // Метод робить перевірку, чи вже не створено екземпляру
  static Singleton* Instance()
  {
    if (_instance == nullptr) // Чи було раніше створено екземпляр класу?
    {
      // якщо ні, то створити один єдиний екземпляр і повернути його.
      _instance = new Singleton();
      return _instance;
    }
    else
    {
      return nullptr;
    }
  }

  // Методи (операції) для доступу до внутрішніх даних класу (поля d)
  int Get()
  {
    return d;
  }

  void Set(int _d)
  {
    d = _d;
  }

  // Метод, що виводить значення внутрішнього поля d
  void Print()
  {
    cout << "d = " << d << endl;
  }

  // Деструктор класу - коректне звільнення пам'яті
  ~Singleton()
  {
    delete _instance;
  }
};

// Ініціалізація внутрішньої статичної змінної _instance,
// згідно з синтаксисом C++
Singleton* Singleton::_instance = nullptr;

void main()
{
  // Демонстрація використання паттерну Singleton
  // Створити єдиний екземпляр класу Singleton
  Singleton* obj1 = Singleton::Instance();

  if (obj1 != nullptr)
  {
    obj1->Set(255);
    obj1->Print(); // d = 255
  }

  // Спроба створення другого екземляру класу Singleton
  Singleton* obj2 = Singleton::Instance(); // obj2 = nullptr

  if (obj2 != nullptr)
  {
    obj2->Set(300);
    obj2->Print();
  }
}

Результат виконання програми

d = 255

 

4. Переваги використання паттерну Singleton

Використання паттерну Singleton дає наступні переваги:

  • контроль в межах класу над створення єдиного екземпляру (певної кількості екземплярів). Це полегшує написання клієнтського коду перевірки;
  • зменшення кількості використовуваних імен в програмі. Це пояснюється тим, що паттерн Singleton замінює збереження унікальних екземплярів класів в глобальних змінних;
  • паттерн допускає успадкування (розширення). Це дозволяє модифікувати методи (операції) у підкласах. У цих підкласах можна зконфігурувати роботу базового класу і отримати потрібний екземпляр під час виконання;
  • допускає змінну кількість екземплярів. Підхід паттерну Singleton можна використати для зміни обмеження на кількість створюваних екземплярів (наприклад, 3 екземпляри) класів. Для цього потрібно перепрограмувати відповідним чином метод Instance().

 

5. Приклад застосування паттерну Singleton для створення не більше трьох екземплярів класів

Паттерн Singleton можна модифікувати таким чином, щоб обмежити створення будь-якої кількості екземплярів класу.

У прикладі продемонстровано підхід паттерну Singleton для створення не більше трьох екземплярів класу з іменем Singleton3.

Облік екземплярів ведеться з допомогою двох внутрішніх змінних:

  • масиву _instance з 3-х покажчиків на тип Singleton3*;
  • змінної count – кількості створених екземплярів.

Текст програми на C++ наступний:

#include <iostream>
using namespace std;

// Реалізація паттерну Singleton на C++.
// Створення не більше трьох екземплярів класу Singleton
class Singleton3
{
private:
  // Статична внутрішня змінна, яка зберігає масив екземплярів класу.
  static Singleton3* _instance[3]; // оголосити 3 покажчики на Singleton3
  static int count; // поточна кількість створених екземплярів

  // Деякі дані класу, тут можуть бути будь-які дані
  int d;

protected:
  // Конструктор класу, оголошений як protected, для того щоб
  // - неможливо було створити екземпляр класу операцією new з коду клієнта;
  // - можна було успадковувати даний клас з інших класів.
  Singleton3()
  {
    d = 0;
  }

public:
  // Статичний метод, який повертає екземпляр класу Singleton.
  // Метод робить перевірку, чи вже не створено екземпляру
  static Singleton3* Instance()
  {
    if (count < 3)
    {
      _instance[count] = (Singleton3*) new Singleton3();
      count++;
      return (Singleton3*)(_instance[count - 1]);
    }
    else
    {
      return nullptr;
    }
  }

  // Методи (операції) для доступу до внутрішніх даних класу (поля d)
  int Get()
  {
    return d;
  }

  void Set(int _d)
  {
    d = _d;
  }

  // Метод, що виводить значення внутрішнього поля d
  void Print()
  {
    cout << "d = " << d << endl;
  }

  // Деструктор класу - коректне звільнення пам'яті
  ~Singleton3()
  {
    if (count > 0)
    {
      for (int i = 0; i < count; i++)
        delete _instance[i];
      delete[] _instance;
    }
  }
};

// Ініціалізація внутрішньої статичної змінної _instance
// та статичного лічильника count.
Singleton3* Singleton3::_instance[] = { nullptr,nullptr,nullptr };
int Singleton3::count = 0;

void main()
{
  // Демонстрація використання паттерну Singleton
  // Створити три екземпляри класу Singleton
  Singleton3* objArray[3];

  for (int i = 0; i < 3; i++)
  {
    // Створити екземпляр
    objArray[i] = Singleton3::Instance();

    if (objArray[i] != nullptr)
    {
      objArray[i]->Set(i * 5);
      objArray[i]->Print();
    }
  }

  // Спроба створити четвертий об'єкт
  Singleton3* obj4 = Singleton3::Instance();
  if (obj4 != nullptr)
  {
    obj4->Set(25);
    obj4->Print();
  }
  else
    cout << "Error of creating obj4." << endl;
}

Результат виконання програми:

d = 0
d = 5
d = 10
Error of creating obj4.

 


Зв’язані теми