Patterns. Паттерн Singleton. Використання у випадку, коли класи-одинаки утворюють ієрархію успадкування. Реалізації на C++, C#, Java




Паттерн 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

 


Зв’язані теми