Patterns. Паттерн Singleton. Использование в случае, когда классы-одиночки образуют иерархию наследования




Паттерн Singleton. Использование в случае, когда классы-одиночки образуют иерархию наследования. Реализации на C++, C#, Java

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


Содержание


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

1. Обобщенная структура паттерна Singleton. Определение проблемы

Перед использованием примера целесообразно рассмотреть упрощенную структуру паттерна Singleton.

Упрощенная структура паттерна Singleton

Рисунок 1. Упрощенная структура паттерна Singleton

В паттерне Singleton, из класса-одиночки могут быть унаследованы подклассы, которые конкретизируют (расширяют) работу методов (операций) базового класса. Возникает задача, которая должна давать ответы на следующие вопросы:

  • как определить унаследованный класс;
  • как сделать, чтобы клиенты могли использовать единственный экземпляр унаследованного класса. Здесь нужно учесть, что из базового класса может быть унаследовано несколько подклассов.

 

2. Порождение и использование подкласса в паттерне Singleton. Пример реализации на C++
2.1. Схема классов, образующих иерархию

В примере демонстрируется использование паттерна Singleton для трех классов A, B, C, которые образуют иерархию наследования изображенную на рисунке 2.

Согласно условию задачи нужно проконтролировать создание не более одного экземпляра каждого класса. То есть, клиент может создать максимум по одному экземпляру каждого из классов (всего 3 экземпляра).

Самым простым вариантом решения задачи является добавление в каждый класс собственного статического экземпляра и статического метода Instance().

 

Паттерн Singleton. Схема решения задачи на C++. Случай наследования классов

Рисунок 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

 


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