Намір

Запевняє, що клас має тільки один примірник, і забезпечує глобальну точку доступу до нього.

Мотивація

Для деяких класів дуже важливо мати тільки один примірник. Хоча у системі можуть бути декілька принтерів, повинна бути тільки одна програма управління потоками принтерів. Повинна бути тільки одна файлова система і один віконний менеджер. Цифровий фільтр буде мати один перетворювач A/D. Система ведення облікових записів призначена для обслуговування однієї компанії. Як ми впевнимось у тому, що клас має тільки один примірник, і що примірник являється легкодоступним? Глобальні змінні роблять об'єкт доступним, але не утримують вас від створення багатьох об'єктів. Кращим рішенням являється розробка класу, який сам відповідає за обрахунок його єдиного примірника. Клас може запевнити, що жоден примірник не може бути створеним (перехватом запитів на створення нових об'єктів), і він може забезпечувати спосіб доступу до примірника. Це шаблон Синглтон.

Застосовуваність

Використовуйте шаблон Синглтон коли
  • повинен бути тільки один примірник класу, і він повинен бути доступним для клієнтів з загальновідомої точки доступу.
  • Коли єдиний примірник повинен бути розширений за допомогою створення потомків і клієнтам необхідно використовувати розширений примірник без модифікування їхнього коду.

Структура

fig.3.fig12_Singleton_structure

Учасники

  • Singleton
    • визначає операцію Instance, яка дозволяє клієнтам отримувати доступ до його унікального примірника. Instance являється операцією класу (тобто, методом класу у Smalltalk і статичним методом у С++).
    • Може бути відповідальним за створення його власних унікальних примірників.

Співробітництво

  • Клієнти отримують доступ до єдиного примірника Сиглтона виключно через операцію Instance.

Наслідки

Шаблон Синглтон має декілька переваг:
  1. Контрольований доступ до єдиного примірника. Через те, що клас Синглтон інкапсулює його єдиний примірник, він може мати строгий контроль над тим, як і коли клієнти отримують доступ до нього.
  2. Зменшений простір імен. Шаблон Синглтон є вдосконаленням глобальних змінних. Він запобігає забруднення простору імен глобальними іменами, які зберігають єдині примірники.
  3. Дозволяє покращення операцій і представлень. Клас Singleton може мати потомки і легко конфігурувати програму з допомогою примірники цього розширеного класу. Ви можете конфігурувати програму за допомогою необхідного примірника класу під час виконання програми.
  4. Дозволяє різну кількість примірників. Шаблон зменшує наслідки від зміни рішення і дозволяє вам дозволити більше ніж один примірник класу Синглтон. Більше того, ви можете використовувати подібний підхід для контролю кількості примірників, які використовує програма. Потребує зміни тільки операція, яка гарантує доступ до примірника Синглтону.
  5. Більш гнучко ніж операції класу. Іншим способом упакування функціональності синглтону являється використання операцій класу (тобто, статичних методів у С++ чи методів класу у Smalltalk). Але обидва цих мовних способів роблять важчим зміну дизайну системи, для того щоб дозволити більш ніж один примірник класу. Більше того, статичні члени у С++ не можуть бути віртуальними, отож потомки не зможуть їх поліморфно перевизначити.

Реалізація

Ось питання реалізації для розгляду під час використання шаблону Синглтон:
  1. Гарантування одного примірника. Шаблон Синглтон робить єдини примірник нормальним примірником класу, але цей клас написаний так, що може бути створеним тільки один його примірник. Поширеним способом його реалізування являється у приховуванні операції, яка створює примірники за операціями класу (тобто, або статичним методом або методом класу), це гарантує тільки те, що створюється тільки один примірник. Ця операція має доступ до змінної, яка утримує унікальний примірник, і вона запевняє, що змінна є ініціалізованою унікальним примірником перед тим, яка повернути її значення. Цей підхід запевняє, що синглтон створений і ініціалізований перед тим, як він вперше використаний. Ви можете визначити операцію класу у С++, як статичний метод Instance класу Singleton. Клас Singleton також визначає статичний поле _instance, яке містить вказівник до його унікальних примірників.
          static Singleton* Instance() ; 
    
      protected: 
    
          Singleton() ; 
    
      private: 
    
          static Singleton* _instance ; 
    } ;
    
    Відповідна реалізація виглядає наступним чином:
    Singleton* Singleton :: _instance = 0;
    Singleton* Singleton :: Instance () 
    { 
          if (_instance == 0) 
          { 
                _instance = new Singleton ; 
          } 
    
          return _instance; 
    
    }
    Клієнти отримують доступ до Синглтона тільки через метод Instance. Змінна _instance ініціалізована у 0і статичний метод Instance повертає її значення, ініціалізуючи її за допомогою унікального примірника, якщо вона встановлена у 0. Instance використовує ліниву ініціалізацію; значення, яке він повертає, не створюється до поки не виконаний перший доступ до змінної. Зверніть увагу на те, що конструктор являється захищеним (protected). Клієнт, який спробує прямо створити примірник класу Singleton отримає помилку під час компілювання. Це запевняє, що може бути створений тільки один примірник. Більше того, оскільки _instance являється вказівником на об'єкт Singleton, метод Instance може зв'язати вказівник потомка класу Singleton з цією змінною. Ми покажемо приклад цього у секції Приклад Коду. Є що одна річ на яку потрібно звернути увагу у реалізації С++. Недостатньо визначити Синглтон як глобальний статичний об'єкт і тоді покладатись на автоматичну ініціалізацію. Існує три причини для цього:
    1. Ми не можемо гарантувати, що буде оголошений тільки один примірник статичного об'єкту.
    2. Ми можемо не мати достатньо інформації для створення примірника кожного Синглтона під час ініціалізації статичних об'єктів. Синглтон може вимагати значення, які обчислені пізніше, під час виконання програми.
    3. С++ не визначає в якому порядку викликаються конструктори крізь одиниці перетворень [ES90]. Це означає, що між синглтонами не може бути ніяких залежностей; якщо будь-які все-таки існують, тоді помилки неминучі.
    Додаткова (хоча мала) відповідальність підходу глобального/статичного об'єкту у тому, що вона змушує усі Синглтони створюватися використовуються вони чи ні. Використання статичних методів запобігає усіх цих проблем. У Smalltalk, функції які повертають унікальний примірник реалізовуються як метод класу класу Singleton. Щоб впевнитись чи створено тільки один примірник, перевизначте нову операцію. Результуючий клас Singleton може мати наступні два методи класу, де SoleInstance являється змінною класу (полем), яка не використовується в іншому місці:
    new 
        self error: 'cannot create new object' 
    
    default 
        SoleInstance isNil ifTrue: [SoleInstance := super new]. 
        ^ SoleInstance</li>
    
  2. Створення потомків класу Singleton. Основна проблема полягає не так як у визначені потомків, але у створені їх унікальних примірниках, щоб клієнти могли їх використовувати. В сутності, змінна, яка відноситься до примірника синглтону повинна бути ініціалізована примірники дочірнього класу. Найпростішою технікою являється визначення який Синглтон ви бажаєте використовувати у операції Instance класу Singleton. Приклад у секції Приклад Коду показує як реалізовувати цю техніку за допомогою змінних середовища. Іншим шляхом для вибору потомка Singleton являється винесення реалізації методу Instance за межі батьківського класу (тобто, MazeFactory) і вкласти її у дочірній клас. Це дозволяє програмісту С++ зробити рішення про клас синглтону під час компонування програми (наприклад, компонуванням у об'єктному файлі, який містить різні реалізації), але тримати це прихованим від клієнтів синглтону. Підхід компонування виправляє вибір класу сиглтону під час компонування програми, який робить важчим вибір класу синглтону під час виконання програми. Використовуючи умовні вирази для визначення потомку являється більш гнучким, а жорстко закодовує набір можливих класів Singleton. Жоден підхід недостатньо гнучкий в усіх випадках. Більш гнучкий підхід використовує реєстр Синглтонів. Замість того, щоб мати Instance, який визначає набір можливих класів Singleton, класи Singleton можуть реєструвати їхні примірники за іменами в загальновідомому реєстрі. Реєстр зв'язує рядкові імена і синглтони. Коли Instance потребує синглтон, він консультується у реєстра, опитуючи синглтон за ім'ям. Реєстр переглядає відповідні синглтони (якщо такі існують) і повертає його. Цей підхід вивільняє метод Instance від інформації про усі можливі класи Singleton чи їх примірники. Усе що він вимагає, це загальновідомий інтерфейс для усіх класів Singleton, що включають операції для реєстру:
    class Singleton 
    { 
    
      public : 
    
          static void Register (const char* name, Singleton*) ; 
          static Singleton* Instance () ; 
    
      protected:
    
          static Singleton* Lookup (const char* name); 
    
      private: 
    
          static Singleton* _instance; 
          static List<NameSingletonPair>* _registry; 
    
    } ;
    Реєстр реєструє примірник Singleton з заданим ім'ям. Для утримування реєстру простим, нам необхідно зберігати список об'єктів NameSingletonPair. Кожен NameSingletonPair зв'язує ім'я з синглтоном. Операція lookup знаходить синглтон за заданим ім'ям. Ми будемо вважати, що змінна середовища вказує ім'я бажаного синглтону.
    Singleton* Singleton::Instance () 
    { 
          if (_instance == 0) 
          { 
                const char* singletonName = getenv("SINGLETON") ; 
    
                // користувач чи середовище надають це 
                // під час запуску програми 
    
                _instance = Lookup(singletonName) ; 
    
                // Lookup повертає 0, якщо 
                // не знайшло відповідного синглтону
          } 
    
          return _instance ; 
    }
    Де класи Singleton реєструють себе? Однією можливістю являється реєстрування у конструкторі. Наприклад, потомок MySingleton може виконувати наступне:
    MySingleton::MySingleton() 
    { 
          // ... 
          Singleton::Register("MySingleton", this); 
    }
    Звичайно, конструктор не буде викликаним аж поки дехто не створить примірник класу, що відголошує проблему, яку шаблон Cинглтон намагається вирішити! Ми можемо обійти цю проблему у С++, визначенням статичного примірника MySingleton. Наприклад, ми можемо оголосити:
    static MySingleton theSingleton;
    у файлі який містить реалізацію MySingleton. Клас Синглтон більше не відповідальний за створення синглтону. Замість того, його основною відповідальністю являється організація доступу в системі до обраного синглтону. Підхід статичного об'єкту все-одно має потенціальний недолік — повинні бути створеними саме ці примірники усіх можливих потомків класу Singleton, інакше вони не будуть зареєстрованими.

Приклад Коду

Припустимо ми визначили клас MazeFactory для побудови лабіринтів, як описано на сторінці 92. MazeFactory визначає інтерфейс для побудови різних частин лабіринту. Потомки можуть перевизначати операції для повернення примірників класів спеціалізованих продуктів, на подобі об'єктів BombedWall замість звичайних об'єктів Wall. Доречним тут являється те, що програма Maze потребує тільки один примірник фабрики лабіринтів і цей примірник повинен бути доступним для коду, який будує будь-яку частину лабіринту. Це випадок коли вступає в дію шаблон Синглтон. Роблячи MazeFactory синглтоном, ми даємо глобальний доступ до об'єкта лабіринту без використання глобальних змінних. Для простоти, припустимо, що ми ніколи не будемо створювати потомки класу MazeFactory. (В майбутньому ми розглянемо альтернативу). Ми зробимо його класом Singleton у С++, додаючи статичну операцію Instance і статичне поле _instance для утримування одного і єдиного примірника. Ми повинні також захистити конструктор для того, щоб запобігти випадкове створення примірника, що може призвести до більш ніж одного примірника.
class MazeFactory 
{ 
  public: 

      static MazeFactory* Instance(); 
      // існуючий інтерфейс оголошується тут

  protected: 

      MazeFactory(); 

  private: 

      static MazeFactory* _instance; 
} ;
І відповідна реалізація:
MazeFactory* MazeFactory::_instance = 0; 

MazeFactory* MazeFactory :: Instance () 
{ 
      if (_instance == 0) 
      { 
             _instance = new MazeFactory; 
      }

      return _instance; 
}
Давайте розглянемо, що трапиться коли будуть існувати потомки класу MazeFactory і програма повинна вирішити, який з них використовувати. Ми оберемо тип лабіринту через змінну середовища і додаємо код, який створює примірник відповідного потомка MazeFactory, спираючись на значення змінної. Операція Instance являється хорошим місцем для розміщення даного коду через те, що вона вже створює примірники класу MazeFactory:
MazeFactory* MazeFactory :: Instance () 
{ 
      if (_instance == 0) 
      { 
            const char* mazeStyle = getenv("MAZESTYLE") ; 

            if (strcmp(mazeStyle, "bombed") == 0) 
            { 
                  _instance = new BombedMazeFactory; 
            } 
            else if (strcmp(mazeStyle, "enchanted") == 0) 
            { 
                  _instance = new EnchantedMazeFactory; 
                  // ... інші можливі потомки
            }
            else
            { 
                  // примірник за замовчуванням
                  _instance = new MazeFactory; 
            } 
      } 

      return _instance; 
}
Зверніть увагу на те, що метод Instance повинний бути модифікованим кожного разу, коли ви визначаєте новий потомок класу MazeFactory. Це може і не бути проблемою в даній програмі, але для абстрактних фабрик визначених у фреймворку може ускладнювати ситуацію. Можливим рішенням буде використання підходу у якому використовується реєстр, який описаний в секції Реалізація. Динамічне компонування може бути так-само корисним — воно утримує програму від завантаження усіх потомків, які не використовуються.

Відомі використання

Прикладом шаблону Синглтон у Smalltalk-80 [Par90] являється набір змін до коду, який поточно називається ChengeSet. Більш точний приклад є відносини між класами і їхніми метакласами. Метаклас являється класом класу і кожен метаклас має один примірник. Метакласи не мають імен (окрім опосередкованого через їхній єдиний примірник), але вони відслідковують їхні єдині примірники і зазвичай не створять нового. Інструментарій інтерфейсу користувача InterViews [LCI+92] використовує шаблон Синглтон для отримування доступу до унікальних примірників його класів Session і WidgetKit, з-поміж інших. Клас Session визначає головний цикл обробки подій, зберігає базу даних користувача стилістичних вподобань і управляє з'єднаннями до одного чи більше фізичних дисплеїв. WidgetKit являється Абстрактною Фабрикою для визначення вигляду і поведінки візуальних віджетів користувача. Операція WidgetKit::instance визначає певний потомок класу WidgetKit, примірник якого створено на основі змінної середовища, яку визначає клас Session. Подібна операція в класі Sessiob визначає чи підтримуються кольоровий, чи монохромний дисплей, і конфігурує примірник синглтона класу Session відповідно.

Споріднені шаблони

Багато шаблонів можуть бути реалізованими використовуючи шаблон Синглтон. Перегляньте Абстрактну Фабрику, Будівник, і Прототип.