Паттерн Singleton. Использование в случае, когда классы-одиночки образуют иерархию наследования. Реализации на C++, C#, Java
Перед изучением данной темы рекомендуется ознакомиться со следующей темой:
Содержание
- 1. Обобщенная структура паттерна Singleton. Определение проблемы
- 2. Порождение и использование подкласса в паттерне Singleton. Пример реализации на C++
- 3. Пример порождения и использования подкласса в паттерне Singleton. Реализация на Java
- 4. Пример применения паттерна Singleton для 3-х классов, образующих иерархию. Реализация на C#
- Связанные темы
Поиск на других ресурсах:
1. Обобщенная структура паттерна Singleton. Определение проблемы
Перед использованием примера целесообразно рассмотреть упрощенную структуру паттерна Singleton.
Рисунок 1. Упрощенная структура паттерна Singleton
В паттерне Singleton, из класса-одиночки могут быть унаследованы подклассы, которые конкретизируют (расширяют) работу методов (операций) базового класса. Возникает задача, которая должна давать ответы на следующие вопросы:
- как определить унаследованный класс;
- как сделать, чтобы клиенты могли использовать единственный экземпляр унаследованного класса. Здесь нужно учесть, что из базового класса может быть унаследовано несколько подклассов.
⇑
2. Порождение и использование подкласса в паттерне Singleton. Пример реализации на C++
2.1. Схема классов, образующих иерархию
В примере демонстрируется использование паттерна Singleton для трех классов A, B, C, которые образуют иерархию наследования изображенную на рисунке 2.
Согласно условию задачи нужно проконтролировать создание не более одного экземпляра каждого класса. То есть, клиент может создать максимум по одному экземпляру каждого из классов (всего 3 экземпляра).
Самым простым вариантом решения задачи является добавление в каждый класс собственного статического экземпляра и статического метода Instance().
Рисунок 2. Схема решения задачи. Иерархия классов A, B, C. Статические методы Instance() в классах
Как видно из рисунка 2, каждый из классов имеет собственный метод Instance(), который создает единственный экземпляр данного класса. В каждом классе объявляется ссылка (указатель) на единственный созданный экземпляр.
⇑
2.2. Текст программы на языке C++
#include <iostream> using namespace std; // Реализация паттерна Singleton на C++. // Демонстрация использования паттерна Singleton // в случае, когда классы образуют иерархию наследования. // Задача. Проконтролировать создание не более одного экземпляра каждого класса, // которые образуют иерархию наследования. class A { private: // Статическая внутренняя переменная, сохраняющая экземпляр класса A. static A* _instance; // Некоторые данные класса, здесь могут быть любые данные int a; protected: // Конструктор A() { a = 0; } public: // Статический метод, возвращающий экземпляр класса A. static A* Instance() { if (_instance == nullptr) { // Создать экземпляр _instance = (A*) new A(); return _instance; } else { return nullptr; } } // Методы (операции) для доступа к внутренним данным класса (поле a) int Get() { return a; } void Set(int _a) { a = _a; } // Метод, выводящий значение внутреннего поля d void Print() { cout << "A.a = " << a << endl; } // Деструктор класса - корректное освобождение памяти ~A() { delete[] _instance; } }; // Класс B - наследует класс A class B : public A { private: static B* _instance; // Некоторые внутренние данные класса B, здесь можно поместить собственные данные int b; protected: // Защищенный конструктор класса B B() { b = 0; } public: // Статический метод, возвращающий экземпляр класса B. static B* Instance() { if (_instance == nullptr) { // Создать экземпляр _instance = (B*) new B(); return _instance; } else { return nullptr; } } // Методы доступа к внутренним данным (переменная b) int Get() { return b; } void Set(int _b) { b = _b; } // Вывести значение внутренней переменной b для контроля void Print() { cout << "B.b = " << b << endl; } // Деструктор класса - коректное освобождение памяти ~B() { delete _instance; } }; // Класс C - наследует класс B class C :B { private: // Экземпляр класса C static C* _instance; // Внутренние данные класса C int c; protected: // Защищенный конструктор класса C C() { c = 0; } public: // Статический метод, возвращающий экземпляр класса C. static C* Instance() { if (_instance == nullptr) { // Создать экземпляр _instance = (C*) new C(); return _instance; } else { return nullptr; } } // Методы доступа к внутренним данным int Get() { return c; } void Set(int c) { this->c = c; } // Вывести значение внутренней переменной c для контроля void Print() { cout << "C.c = " << c << endl; } // Деструктор класса - корректное освобождение памяти ~C() { delete _instance; } }; // Инициализация внутренней статической переменной _instance // в каждом из классов. A* A::_instance = nullptr; B* B::_instance = nullptr; C* C::_instance = nullptr; void main() { // Демонстрация использования паттерна Singleton для унаследованных классов // 1. Создать экземпляр класса A A* objA; objA = A::Instance(); if (objA != nullptr) { // если экземпляр создан, то записать в него число 330 objA->Set(330); objA->Print(); } else cout << "objA == nullptr" << endl; // 2. Попытка создать еще один экземпляр класса A A* objA2; objA2 = A::Instance(); if (objA2 != nullptr) { objA2->Set(550); objA2->Print(); } else cout << "objA2 == nullptr" << endl; // 3. Создать экземпляр класса B B* objB; objB = (B*) B::Instance(); if (objB != nullptr) { objB->Set(770); objB->Print(); objA->Print(); } else cout << "objB == nullptr" << endl; // 4. Попытка создания еще одного экземпляра класса B B* objB2 = (B*)B::Instance(); if (objB2 != nullptr) { objB2->Set(2222); objB2->Print(); } else cout << "objB2 == nullptr" << endl; // 5. Создать экземпляр класса C C* objC = (C*)C::Instance(); if (objC != nullptr) { objC->Set(880); objC->Print(); // вывести значение экземпляров objA, objB снова objA->Print(); objB->Print(); } else cout << "objC == nullptr" << endl; }
⇑
2.3. Результат выполнения программы
Результат выполнения программы (п. 2.2) следующий:
A.a = 330 objA2 == nullptr B.b = 770 A.a = 330 objB2 == nullptr C.c = 880 A.a = 330 B.b = 770
⇑
3. Пример порождения и использования подкласса в паттерне Singleton. Реализация на Java
В данном примере приведена реализация решении задачи на языке Java для трех классов A, B, C образующих иерархию наследования. Подход к решению задачи, так же как и в предыдущем примере на C++ (смотрите рисунок 2).
3.1. Текст программы
// Реализация паттерна Singleton на Java для классов, // которые образуют иерархию. // Суперкласс A class A { private static A _instance=null; // ссылка на экземпляр класса A private int a; // внутренние данные // Защищенный конструктор класса protected A() { a = 0; } // Метод, возвращающий клиенту единственный экземпляр класса A public static A Instance() { if (_instance==null) { _instance = new A(); // создать экземпляр класса A return _instance; } else return null; } // Методы доступа к данным класса public void Set(int _a) { a = _a; } public int Get() { return a; } // Метод вывода внутренних данных для контроля public void Print() { System.out.println("A.a = " + a); } } // Класс B - наследует класс A class B extends A { private static B _instance=null; // экземпляр класса B private int b; // внутренние данные // защищенный конструктор класса B protected B() { b = 0; // инициализировать внутренние данные } public static B Instance() { if (_instance==null) { _instance = new B(); return _instance; } else return null; } // Методы доступа к внутренним данным класса public void Set(int _b) { b = _b; } public int Get() { return b; } // Метод вывода данных на экран public void Print() { System.out.println("B.b = " + b); } } // Класс C - наследует клас B class C extends B { private static C _instance=null; private int c; // внутренние данные // Защищенный конструктор класса C protected C() { c = 0; } // Метод, возвращающий экземпляр класса C public static C Instance() { if (_instance==null) { _instance = new C(); return _instance; } else return null; } // Методы доступа public void Set(int _c) { c = _c; } public int Get() { return c; } // Метод вывода данных на экран public void Print() { System.out.println("C.c = " + c); } } public class TestSingleton { public static void main(String[] args) { // Демонстрация использования паттерна Singleton для 3-х классов, // которые создают иерархию // 1. Создать экземпляр класса C C objC = C.Instance(); if (objC!=null) { objC.Set(255); objC.Print(); // C.c = 255 } else System.out.println("objC == null"); // 2. Попытка создания другого екземпляра класса C C objC2 = C.Instance(); if (objC2!=null) { objC2.Set(550); objC2.Print(); } else System.out.println("objC2 == null"); // 3. Создание экземпляра класса A A objA = A.Instance(); if (objA!=null) { objA.Set(780); objA.Print(); } else System.out.println("objA == null"); // 4. Повторное создание экземпляра класса A A objA2 = A.Instance(); if (objA2!=null) { objA2.Set(1000); objA2.Print(); } else System.out.println("objA2 == null"); } }
⇑
3.2. Результат выполнения программы
C.c = 255 objC2 == null A.a = 780 objA2 == null
⇑
4. Пример применения паттерна Singleton для 3-х классов, образующих иерархию. Реализация на C#
В примере реализовано использование паттерна Singleton по образцу, изображенное на рисунке 2 (решение на C++).
4.1. Текст программы
using System; namespace ConsoleApp9 { // Базовый класс в иерархии классов class A { private static A _instance = null; // ссылка на экземпляр класса private int a; // внутренние данные // Защищенный конструктор класса protected A() { a = 0; } // Метод, возвращающий экземпляр класса public static A Instance() { if (_instance==null) { _instance = new A(); return _instance; } return null; } // Методы доступа к полю a public int Get() { return a; } public void Set(int _a) { a = _a; } // Метод, выводящий значение a на экран public void Print() { Console.WriteLine("A.a = {0}", a); } } // Класс, унаследованный от класса A class B:A { private static B _instance; // экземпляр класса B private int b; // внутренние данные // защищенный конструктор класса protected B() { b = 0; } // Метод, возвращающий экземпляр класса public static new B Instance() { if (_instance == null) { _instance = new B(); return _instance; } else return null; } // Методы доступа к полю b public new int Get() { return b; } public new void Set(int _b) { b = _b; } // Метод, выводящий значение b на экран public new void Print() { Console.WriteLine("B.b = {0}", b); } } // Класс, наследующий класс B class C:B { private static C _instance; // экземпляр класса C private int c; // внутренние данные // защищенный конструктор класса protected C() { c = 0; } // Метод, возвращающий экземпляр класса public static new C Instance() { if (_instance == null) { _instance = new C(); return _instance; } else return null; } // Методы доступа к полю c public new int Get() { return c; } public new void Set(int _c) { c = _c; } // Метод, выводящий значение c на экран public new void Print() { Console.WriteLine("C.c = {0}", c); } } // Класс, который выступает клиентом class Program { static void Main(string[] args) { // Демонстрация паттерна Singleton для 3-х унаследованных классов // 1. Создать экземпляр класса B B objB = B.Instance(); if (objB != null) { objB.Set(330); objB.Print(); } else Console.WriteLine("objB == null"); // 2. Повторно создать экземпляр класса B B objB2 = B.Instance(); if (objB2 != null) { objB2.Set(700); objB2.Print(); } else Console.WriteLine("objB2 == null"); // 3. Создать экземпляр класса C C objC = C.Instance(); if (objC != null) { objC.Set(880); objC.Print(); } else Console.WriteLine("objC == null"); // 4. Попытка повторного создания экземпляра класса C C objC2 = C.Instance(); if (objC2 != null) { objC2.Set(990); objC2.Print(); } else Console.WriteLine("objC2 == null"); } } }
⇑
4.2. Результат выполнения программы
После выполнения программа (смотрите п. 4.1.) выдаст следующий результат:
B.b = 330 objB2 == null C.c = 880 objC2 == null
⇑
Связанные темы
⇑