Как реализовать singleton?
Да.. Мы знаем, что не стоит использовать этот паттерн и сколько бы эту тему не прочесали до меня, я, пожалуй, вставлю свое слово. А именно, слово про реализацию. Хоть я и не скажу ничего нового, но как минимум зафиксирую этот момент для себя.
Чтож.. В поиске реализаций класса Singleton мы обычно обращаемся к статьям, книгам или ищем примеры на гитхабе и, в итоге, натыкаемся на что-то подобное:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
* C++ Design Patterns: Singleton
* Author: Jakub Vojvoda [github.com/JakubVojvoda]
* 2016
*/
class Singleton
{
public:
Singleton( Singleton const& ) = delete;
Singleton& operator=( Singleton const& ) = delete;
static Singleton* get()
{
if ( !instance ) {
instance = new Singleton();
}
return instance;
}
static void restart()
{
if ( instance ) {
delete instance;
}
}
void tell()
{
std::cout << "This is Singleton." << std::endl;
}
private:
Singleton() {}
static Singleton *instance;
};
Singleton* Singleton::instance = nullptr;
int main()
{
Singleton::get()->tell();
Singleton::restart();
}
И обычно сходу берем его на вооружение.
Да. Это типичный пример singleton-а. И на самом деле ничего сверхплохого в данной реализации нет. Но через какое-то время в этом примере можно увидеть некоторый изъян реализации.
Давайте рассмотрим следующую ситуацию. Вы пишете программу. И в вашей программе должен быть кот. И кот должен быть один. Совсем один. И вы решаете делать его классом одиночкой. Например, по вышеприведенному шаблону. Некоторый дискомфорт можно почувствовать при попытке придумать название класса. Ведь просто Cat не подойдет, это ведь кот одиночка. А что тогда подойдет? SingleCat? LonelyKitten?
К тому же, зная про такую штуку как SOLID мы увидим, что этот класс будет противоречить первому принципу. А именно The Single Responsibility Principle (Принцип единственной ответственности). То есть наш класс будет не только котом, но еще и одиночкой.
А во вторых, у нас может возникнуть потребность создать собаку, гуся, корову и чтобы все они были синглтонами. Или чтобы из трех собак только одна собака была синглтоном, а остальные были обычными.
И что нам нужно делать? Правильно! Делить ответственность! Прибегая к помощи шаблонов создаем класс Singleton, instance которого будет иметь шаблонный тип. И выглядеть это может примерно следующим образом.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ifndef SINGLETON_HPP_
#define SINGLETON_HPP_
template<class T>
class Singleton {
public:
static T* Instance();
private:
Singleton();
Singleton(Singleton<T> const&);
Singleton<T>& operator= (const Singleton<T>& t);
virtual ~Singleton();
static T* instance;
};
template<class T>
T* Singleton<T>::instance = NULL;
template<class T>
T* Singleton<T>::Instance()
{
if (instance == NULL) {
instance = new T;
}
return instance;
}
#endif /* SINGLETON_H_ */
И в будущем при реализации кота мы будем думать только про кота, не заботясь о том, как делать его синглтоном.
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <Singleton.hpp>
class Cat
{
public:
Cat() { }
~Cat() { }
void meow()
{
std::cout << "Meow" << std::endl;
}
};
А сделать так, чтобы появился кот синглтон (или собака) проще простого.
1
2
3
4
5
Cat* dog = Singleton<Cat>::Instance();
cat->meow();
Dog* dog = Singleton<Dog>::Instance();
dog->gav();
С таким же успехом можно создать одиночный класс std::string или, например, std::vector.
1
std::string* s = Singleton<std::string>::Instance();
Зачем? Это просто пример! В реальной жизни это может быть класс для подключения к базе данных.