Паттерн Singleton (Одиночка). Обзор. Особенности применения. Реализация на C++
Перед изучением данной темы рекомендуется ознакомиться со следующей темой:
Содержание
- 1. Паттерн Singleton (одиночка). Общие сведения. Обязанности. Случаи применения
- 2. Структурная схема паттерна Singleton
- 3. Пример реализации паттерна Singleton на C++. Создание одного экземпляра класса
- 4. Преимущества использования паттерна Singleton
- 5. Пример применения паттерна Singleton для создания не более трех экземпляров классов
- Связанные темы
Поиск на других ресурсах:
1. Паттерн Singleton (одиночка). Общие сведения. Обязанности. Случаи применения
Паттерн Singleton (одиночка, одиночка) относится к порождающим паттернам. Паттерн Singleton предназначен для создания заданного количества экземпляров (объектов) класса. Чаще всего паттерн Singleton используется для создания гарантированно одного экземпляра класса.
Паттерн Singleton важен в случаях, когда для некоторых классов нужно чтобы существовало определенное количество экземпляров, например один экземпляр.
Использование глобальных переменных не обеспечивает ограничение на создание дополнительных экземпляров классов. Глобальная переменная дает доступ к объекту, но не запрещает создавать несколько экземпляров класса. Поэтому, целесообразно эти ограничения реализовать в самом классе. Именно такой подход предлагает паттерн Singleton.
В паттерне Singleton на класс накладываются следующие обязанности:
- контроль количества экземпляров согласно условию задачи;
- возвращение нужного количества экземпляров по требованию;
- ведение учета полученных экземпляров.
Случаи применения паттерна Singleton:
- когда нужно создать ровно один экземпляр класса;
- когда нужно создать не более заданного количества экземпляров класса;
- когда нужно расширить единственный экземпляр класса производными подклассами. В этом случае клиент имеет возможность использовать расширенные средства (операции, методы) подклассов без модификации своего кода.
⇑
2. Структурная схема паттерна Singleton
Чаще всего, в литературе можно встретить следующую структурную схему паттерна Singleton (рисунок 1).
Рисунок 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.
⇑
Связанные темы
⇑