Одинак (шаблон проєктування)
Одинак (англ. Singleton) — шаблон проєктування, належить до класу твірних шаблонів. Гарантує, що клас матиме тільки один екземпляр, і забезпечує глобальну точку доступу до цього екземпляра.
Через низку притаманних недоліків деякі розробники вважають його антипатерном або «душком» коду.
Для деяких класів важливо, щоб існував тільки один екземпляр. Наприклад, хоча у системі може існувати декілька принтерів, може бути тільки один спулер. Повинна бути тільки одна файлова система та тільки один активний віконний менеджер.
Глобальна змінна не вирішує такої проблеми, бо не забороняє створити інші екземпляри класу.
Рішення полягає в тому, щоб сам клас контролював свою «унікальність», забороняючи створення нових екземплярів, та сам забезпечував єдину точку доступу. Це є призначенням шаблону Одинак.
Одинак в об'єктно-орієнтованому програмуванні можна порівняти з глобальною змінною у процедурному програмуванні з відповідними недоліками[1][2].
Зокрема, цей шаблон створює неявні залежності між функціями та методами, які не декларуються в їхньому інтерфейсі[1][3].
Крім того, Одинак може створювати проблеми під час модульного тестування адже, на додачу до всього іншого, його важко замінити на фіктивну реалізацію (англ. mock)[1][4][5].
Ці та інші недоліки спонукали Еріха Гаму в інтерв'ю 2009 року визнати, що можливо він би відмовився від цього шаблону проєктування, адже вважає його використання одним із «душків» коду[1].
На думку Брайана Батона Одинак порушує один із принципів SOLID: принцип єдиної відповідальності, оскільки окрім власне бізнес логіки, на клас покладається відповідальність за виконання вимог до Одинака. Натомість, він пропонує виокремити функціональність Одинака в інший клас, наприклад, із використанням шаблону фабричного метода або абстрактної фабрики[6].
Деякі з перелічених вад можна позбутись застосуванням шаблону впровадження залежностей та принципу інверсії залежностей[1].
Слід використовувати шаблон Одинак коли:
- повинен бути тільки один екземпляр деякого класу, що легко доступний всім клієнтам;
- єдиний екземпляр повинен розширюватись шляхом успадкування, та клієнтам потрібно мати можливість працювати з розширеним екземпляром не змінюючи свій код.
- Singleton — одинак:
- визначає операцію Instance, котра дозволяє клієнтам отримувати доступ до єдиного екземпляру. Instance — це операція класу;
- може нести відповідальність за створення власного унікального екземпляру.
Клієнти отримують доступ до єдиного об'єкта класу Singleton лише через його операцію Instance.
// Заголовний файл (.h)
//
// Ниттєбезпечна реалізація Одинака
//
// Ця версія виглядає оманливо просто, проте вона має застереження:
// - Перше, якщо одинак міститься в якійсь бібліотеці. Ті, хто використовуватимуть цю бібліотеку
// матимуть екземпляр одинака протягом роботи застосунка, незалежно
// від того, чи використовується він чи ні.
//
// - Друге, це випадок статичних залежностей файлів. Наприклад, уявімо що
// Singleton є якоюсь абстрактною фабрикою для типа BaseType, і
// застосовується метод create. Оскільки порядок ініціалізації статичних змінних
// протягом трансляції модулів не визначений, це може призвести
// до доступу до Одинака до моменту його конструювання, іншими словами
// до невизначеної поведінки, що є погана річ.
//
// namespace { const BaseType * const fileStaticVariable = Singleton::getInstance().create(); }
//
class Singleton
{
private:
static Singleton _instance;
Singleton() {}
~Singleton() {}
Singleton(const Singleton &);
Singleton & operator=(const Singleton &);
public:
static Singleton& getInstance();
};
// Джерельний файл (.cpp)
// Ініціалізація статичного члена.
Singleton Singleton::_instance;
Singleton& Singleton::getInstance()
{
return _instance;
}
Так званий «Одинак Маєрса» покладається на запроваджені у версії C++11 гарантії ниттєбезпечного ініціалізації статичної змінної в тілі методу[7]. Таким чином, простий та ниттєбезпечний Одинак в реалізації Скота Маєрса (англ. Scott Meyers) виглядає так:[1]
#ifndef SINGLETON_H_
#define SINGLETON_H_
class Singleton final {
public:
// Метод повертає посилання на єдиний екземпляр класу, який зберігається
// в локальній статичній змінній
static Singleton& getInstance() {
// єдиний екземпляр цього класу зберігатиметься у локальній статичній змінній
// на противагу попередньому прикладу, ініціалізація екземпляру гарантовано
// відбудеться тільки під час першого виклику цього метода (ліниво). Тобто,
// якщо цей метод ніколи не буде викликаний, то і екземпляр не буде створено
static Singleton theInstance { };
return theInstance;
}
private:
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton& operator=(Singleton&&) = delete;
// ...
};
#endif
Окрім нього, існують й інші варіанти шаблону Одинак без деяких його недоліків[8].
Enum singleton, починаючи з Java 1.5:
public enum SingletonEnum {
INSTANCE;
}
Проста реалізація зі створенням об'єкта під час завантаження його класу[9]
public class Singleton {
private Singleton(){}
private static final Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
Реалізація з відкладеним (ледачим) створенням об'єкта за потреби із синхронізацією для багатопоточної безпеки[9]
public class Singleton {
private Singleton(){}
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance==null) {
instance = new Singleton();
}
return instance;
}
}
Реалізація з відкладеним (ледачим) створенням об'єкта за потреби, яка є багатопоточно безпечною, використовується шаблон Initialization on demand holder. Проблемою реалізації є те, що коли виникає помилка з киданням винятку під час виконання статичної ініціалізації, клас не завантажується і екземпляр одинака більше неможливо створити.
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
Якщо система розподілена, кожна JVM може мати свій екземпляр Одинака. У EJB 3.1 з'явилась анотація @Singleton, яка забезпечує унікальність Одинака на різних JVM[10]
@Singleton
public class SingletonA {
}
Особливістю реалізації засобами мови програмування є необхідність для клієнта використовувати не конструктор, а фабричний метод для одержання об'єкта.
За допомогою засобів аспектно-орієнтованого програмування можна зробити реалізацію Одинака з використанням конструктора.
Реалізація за допомогою AspectJ [11]
public abstract aspect AbstractSingletonAspect {
private Object singleton = null;
abstract pointcut singletonPointcut();
Object around(): singletonPointcut() {
if (singleton == null) {
singleton = proceed();
}
return singleton;
}
}
public aspect SingletonAspect extends AbstractSingletonAspect {
pointcut singletonPointcut() : call(Stats.new(..));
}
Реалізація за допомогою JBoss AOP[11]. Код аспекта:
package aop.patterns.singleton;
import org.jboss.aop.advice.Interceptor;
import org.jboss.aop.joinpoint.Invocation;
public class SingletonInterceptor implements Interceptor {
private Object singleton;
public Object invoke(Invocation invocation) throws Throwable {
if (singleton==null) {
singleton = invocation.invokeNext();
}
return singleton;
}
}
Конфігурація у jboss-aop.xml
<bind pointcut="execution(aop.patterns.singleton.Singleton->new())">
<interceptor class="aop.patterns.singleton.SingletonInterceptor" />
</bind>
package {
public class Singleton {
private static var _instance:Singleton = new Singleton();
public function Singleton () {
if (_instance){
throw new Error(
"Singleton can only be accessed through Singleton.getInstance()"
);
}
}
public static function getInstance():Singleton {
return _instance;
}
}
}
Мова програмування Ruby має вбудовану підтримку деяких шаблонів[12], зокрема й Одинака.
Модуль Singleton з бібліотеки singleton робить конструктор приватним та надає фабричний метод.
require 'singleton'
class MyClass
include Singleton
end
a = MyClass.instance
У Scala шаблон підтримується засобами мови:
object Singleton
// Використання:
val singleton = Singleton
new
self error: 'cannot create new object'
default
SoleInstance isNil ifTrue: [SoleInstance := super new].
^ SoleInstance
Реалізація на PHP
<?php
class Singleton {
// object instance
private static $instance;
private function __construct() {}
private function __clone() {}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self;
}
return self::$instance;
}
public function doAction() {
...
}
}
//usage
Singleton::getInstance()->doAction();
?>
Для Delphi 2005 та вище підходить наступний приклад:
type
TSingleton = class
strict private
class var
Instance: TSingleton;
public
class function NewInstance: TObject; override;
end;
class function TSingleton.NewInstance: TObject;
begin
if not Assigned(Instance) then
Instance := TSingleton(inherited NewInstance);
NewInstance := Instance;
end;
Для більш ранніх версій треба перемістити код класу до окремого модулю, а оголошення Instance
замінити оголошенням глобальної змінної в його секції implementation
(до Delphi 7 включно секції class var
та strict private
були відсутніми).
- ↑ а б в г д е Stephan Roth (2017). The Singleton Anti-Pattern. Clean C++. Apress. doi:10.1007/978-1-4842-2793-0. ISBN 978-1-4842-2792-3.
- ↑ Jim Hyslop, Herb Sutter (1 березня 2003). Once Is Not Enough. Dr. Dobbs Joural.
- ↑ Miško Hevery (17 серпня 2008). Singletons are Pathological Liars. Google Testing blog.
- ↑ Miško Hevery. Flaw: Brittle Global State & Singletons. The Testability Explorer Blog. Архів оригіналу за 24 травня 2022. Процитовано 10 травня 2022.
- ↑ Michael C. Feathers (2005). The Case of the Irritating Global Dependency. Working Effectively with Legacy Code. Prentice Hall. ISBN 0-13-117705-2.
- ↑ Klaus Iglberger (15 травня 2016). Thread-Safe Initialization of Data. Modernes CPP.
- ↑ Peter Muldoon (20 вересня 2020). Retiring the Singleton Pattern (PDF).
- ↑ а б Peter Haggar, IBM developerWorks: Double-checked locking and the Singleton pattern
- ↑ Singleton EJB
- ↑ а б Foundations of AOP for J2EE Development. Renaud Pawlak, Lionel Seinturier, and Jean-Philippe Retaillé. Apress 2005. ISBN 1-59059-507-6.
- ↑ Архівована копія. Архів оригіналу за 13 травня 2007. Процитовано 8 травня 2007.
{{cite web}}
: Обслуговування CS1: Сторінки з текстом «archived copy» як значення параметру title (посилання) - ↑ Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Addison-Wesley, 1994. ISBN 0-201-63361-2
- Singleton Pattern & its implementation with C++
- IBM developerWorks, Use your singletons wisely
- Singleton Pattern: A review and analysis of existing C++ implementations
- Паттерн Singleton на js
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.
- Шаблони проєктування програмного забезпечення
- Статті з прикладами коду мовою C++
- Статті з прикладами коду мовою XML
- Статті з прикладами коду мовою ActionScript
- Статті з прикладами коду мовою Scala
- Статті з прикладами коду мовою Smalltalk
- Статті з прикладами коду мовою PHP
- Статті з прикладами коду мовою Pascal
- Статті з прикладами коду мовою Java
- Статті з прикладами коду мовою Ruby