- Абстрактна фабрика (Abstract factory)
- Будівельник (Builder)
- Фабричний метод (Factory method)
- Прототип (Prototype)
- Одинак (Singleton)
Одинак (або синглтон) – останній зі списку породжуючих патернів. Саме про нього піде мова у статті.
Синглтон (Singleton)
Хоча singleton перекладається як одинак, українські програмісти не звикли використовувати таку назву і називають такий патерн просто синглтоном.
Синглтон фактично є найпростішим патерном, про нього запитують майже на всіх співбесідах і про нього вже чули ті, хто ще не знайомий з іншими дизайн-патернами.
Призначення: створення єдиного екземпляра класу і забезпечення доступу до нього в глобальному контексті.
Часто стає в нагоді при проектуванні програми, в якій повинні бути спільні ресурси для всіх її компонентів. Наприклад, створення системи логування, яка записуватиме всі дані в єдиний простір, попри те, в якому місці програми викликаються її методи. Широко застосовується при написанні драйверів.
Для того, щоби обмежити створення об'єктів, достатньо зробити конструктор приватним, що якраз і є чіткою ознакою синглтона.
Загалом класичний синглтон має вигляд:
class ClassicSingleton
{
public:
static ClassicSingleton* getInstance();
private:
ClassicSingleton();
ClassicSingleton(const ClassicSingleton& cs) = delete;
ClassicSingleton& operator=(const ClassicSingleton& cs) = delete;
~ClassicSingleton();
static ClassicSingleton* m_pInstance;
};
Як видно, конструктор і деструктор приватні. При цьому конструктор копіювання й operator=
не повинні бути реалізовані. Це вказується за допомогою delete
. В приватному просторі також створюється об'єкт даного класу – m_pInstance
. Це єдиний екземпляр класу, який повертатиметься за допомогою функції getInstance()
, реалізація якої наступна:
ClassicSingleton* ClassicSingleton::m_pInstance = 0;
ClassicSingleton* ClassicSingleton::getInstance()
{
if (m_pInstance == nullptr)
m_pInstance = new ClassicSingleton();
return m_pInstance;
}
Якщо умова m_pInstance == nullptr
істинна (а вона буде істинна при першому виклику функції getInstance()
), то викликається конструктор, який ініціалізує об'єкт m_pInstance
, після чого функція повертає вказівник на нього. В іншому випадку, якщо умова не істинна (а це означає, що m_pInstance
вже сконструйований), повертається тільки вказівник на вже створений об'єкт.
Такий код не новий, але актуальний і навряд чи застаріє колись.
Другий спосіб написання синглтона – Синглтон Майєрса:
class MeyersSingleton
{
public:
static MeyersSingleton& getInstance();
private:
MeyersSingleton();
MeyersSingleton(const MeyersSingleton& ms) = delete;
MeyersSingleton& operator=(const MeyersSingleton& ms) = delete;
~MeyersSingleton();
};
Цей спосіб дуже лаконічно використовує можливості мови С++. В даному випадку функція getInstance()
повертає посилання на об'єкт класу, а не вказівник, як у класичному синглтоні. А реалізація цієї функції наступна:
MeyersSingleton& MeyersSingleton::getInstance()
{
static MeyersSingleton instance;
return instance;
}
Коротко і просто. Статичні дані можна ініціалізувати тільки один раз, чим Майєрс і скористався. При першому виклику функції getInstance()
створиться об'єкт instance
і повернеться посилання на нього. При другому і наступних викликах об'єкт instance
вже не створюватиметься, оскільки він статичний. Тоді просто повертатиметься посилання на об'єкт, який вже створено.
Розглянемо принцип роботи синглтона більш детально.
class ClassicSingleton
{
public:
static ClassicSingleton* getInstance();
void setValue(int val);
void outputVal();
private:
ClassicSingleton();
ClassicSingleton(const ClassicSingleton& cs) = delete;
ClassicSingleton& operator=(const ClassicSingleton& cs) = delete;
~ClassicSingleton();
static ClassicSingleton* m_pInstance;
int m_someval;
};
Розширимо клас ClassicSingleton
, додавши член даних m_someval
, який зберігатиме якесь число. А також функцію setValue(int val)
, котра встановлюватиме значення для m_someval
і другу функцію outputVal()
, яка виводитиме дане число.
Заодно розглянемо використання синглтона Майєрса.
int main()
{
int value;
cout << "Stage 1" << endl;
ClassicSingleton* cs = ClassicSingleton::getInstance();
cout << "Classic cs (address): " << cs << endl;
cout << "Set m_someval of cs: ";
cin >> value;
cs->setValue(value);
cout << "Get m_someval of cs: ";
cs->outputVal();
cout << "\
Stage 2" << endl;
MeyersSingleton& ms = MeyersSingleton::getInstance();
cout << "Meyers' ms (address): " << &ms << endl;
cout << "\
Stage 3" << endl;
ClassicSingleton* cs2 = ClassicSingleton::getInstance();
cout << "Classic cs2 (address): " << cs2 << endl;
cout << "Get m_someval of cs2: ";
cs2->outputVal();
cout << "\
Stage 4" << endl;
MeyersSingleton& ms2 = MeyersSingleton::getInstance();
cout << "Meyers' ms (address): " << &ms2 << endl;
return 0;
}
Результат виконання:
Етап 1:
Створюється об'єкт cs
. Зверніть увагу на процес інстанціювання:
ClassicSingleton* cs = ClassicSingleton::getInstance();
getInstance()
повертає вказівник на єдиний екземпляр класу m_pInstance
, при цьому звернення до функції відбувається через ім'я класу. Це значить, отримати доступ до екземпляра синглтона можна на глобальному рівні.
Після створення виводиться адреса, яку тримає cs
, а саме адреса m_pInstance
.
Далі вводиться значення value
- 26, яке передається через setValue(value)
до члена даних класу і згодом виводиться.
Етап 2:
Створюється екземпляр синглтона Майєрса і виводиться його адреса.
Етап 3:
Створюється ще один об'єкт класу ClassicSingleton
– cs2
. Після виводу адреси, бачимо що вона така ж як і в першому етапі. Отже, як і говорилося, екземпляр класу єдиний, а всі інші об'єкти того самого класу вказують на цей єдиний екземпляр.
Викликавши функцію cs2->outputVal()
, отримаємо число 26, як і в першому випадку.
Етап 4:
Теж саме і для ms2
– адреса збігається з тією, що отримана на другому етапі.
Синглтон Майєрса більш безпечний, оскільки в класичному синглтоні необхідно слідкувати за пам'яттю. Однак і синглтон Майєрса може створити проблеми при багатопоточності та інстанціюванні похідних класів. Але це окрема і надто велика тема для розгляду в цій статті.
Підсумки
Синглтон забезпечує створення єдиного екземпляра класу і глобального доступу до нього.
Алгоритм використання:
- Оголосити конструктор класу приватним, та заборонити визначення конструктора копіювання і оператора присвоєння.
- Створити статичний екземпляр (для класичного синглтона) та функцію, що керує ініціалізацією та повертає адресу єдиного екземпляра.
- Отримувати доступ до екземпляра класу на глобальному рівні за допомогою імені класу.
Вихідний код до статті доступний тут.
Ще немає коментарів