Patterns. Паттерн Singleton (Одиночка). Обзор. Особенности применения. Реализация на C++




Паттерн Singleton (Одиночка). Обзор. Особенности применения. Реализация на C++

Перед изучением данной темы рекомендуется ознакомиться со следующей темой:


Содержание


Поиск на других ресурсах:

1. Паттерн 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.

 


Связанные темы