Уникат (пројектни узорак)
У софтверском инжењерству, уникат (енгл. singleton) је пројектни узорак којим се обезбеђује да класа има само једну инстанцу. Ово је корисно када је тачно један објекат потребан да координише акције у систему. Овај концепт се некад генерализује на целе системе који раде ефикасније када постоји само један објекат, или на ограничавање инстанцирања, не на један, него на тачно одређен број објеката (нпр. пет). Овај пројектни узорак неки сматрају и антиузорком јер се превише користи, уноси непотребна ограничења када само једна инстанца класа уопште ни није потребна и уноси глобално стање у програм. [1] [2] [3] [4] [5] [6]
Опште употребе
[уреди | уреди извор]- Пројектни узорци апстрактне фабрике, градитеља и прототипа могу да користе уникат у њиховим имплементацијама.
- Објекти фасаде су најчешће уникати јер је углавном потребан само један објекат фасаде.
- Објекти који чувају стање су често уникати.
- Уникати се углавном преферирају у односу на глобалне променљиве јер:
- Не загађују глобални именски простор (а у језицима који имају именске просторе, простор у коме су садржани) непотребним променљивама.[7]
- Омогућавају лењу алокацију и иницијализацију док глобалне променљиве у многим језицима увек заузимају ресурсе, коришћене или не.
Дијаграм класа
[уреди | уреди извор]Имплементација
[уреди | уреди извор]Имплементација уникат пројектног узорка мора да задовољи услов да се не може направити више од једне инстанце једне исте класе, као и услов да приступ тој инстанци буде глобално доступан. Потребан је и механизам приступа чланицама уникатне класе без креирања нове инстанце уколико она већ не постоји. Уколико инстанца већ постоји, потребно је само вратити референцу на њу. Да би се осигурало да се објекат не може инстанцирати на неки други начин, обично се конструктор прави да буде заштићен (не и приватан, јер поновна употреба или јединични тестови могу тражити приступ конструктору). Приметити разлику између обичне статичке инстанце класе и униката: иако се уникат може имплементирати као статичка инстанца, такође се може и лењо иницијализовати, тј. тако да не захтева никакве меморијске или друге ресурсе док они не буду стварно потребни. Друга битна разлика је да статичке класе не могу да имплементирају интерфејс, осим ако тај интерфејс није обичан маркер. Тако да, ако класа мора да изложи неки уговор кроз интерфејс, мора се користити уникат пројектни узорак, а не статичка инстанца.
Уникат пројектни узорак се мора пажљиво правити у апликацијама које користе више нити. Уколико две нити у исто време покушају да позову методу инстанцирања уникатне класе која још не постоји, обе морају да провере постојање инстанце униката, а онда само једна од те две нити треба да креира инстанцу. Ако програмски језик има могућности конкурентне обраде, овај метод треба бити тако написан да се користи операција узајамног искључивања (енгл. mutually exclusive). Класично решење је да се користи узајамно искључивање над класом која указује да је објекат униката инстанциран.
Примери имплементација
[уреди | уреди извор]Јава
[уреди | уреди извор]Сва решења изнета овде за програмски језик Јаву су нитно безбедна, али се разликују у подржаним верзијама језика и по томе да ли имају лењу иницијализацију.
Стандардни прости начин
[уреди | уреди извор]Ово решење је нитно безбедно без додатних језичких конструкција, али не обезбеђује лењу иницјализацију. Променљива INSTANCE се креира чим се класа Singleton инстанцира(језик: енглески). Ово може бити много пре него што се позове метода getInstance(), на пример када се позове нека друга статичка метода класе. Ако лења иницијализација није потребна или је свеједно када ће се инстанца креирати, или ваша класа нема других статичких чланица или метода који могу случајно да креирају инстанцу класе, онда се може користити следеће решење:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
// privatni konstruktor zabranjuje instanciranje iz drugih klasa
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
Решење Била Пуга
[уреди | уреди извор]Истраживач Бил Пуг са Универзитета у Мериленду је истраживао уникат пројектни узорак у Јави и проблеме са њим. Пугови напори на превазилажењу проблема двоструког испитивања при закључавању (енгл. Double-checked locking) су довели до промене меморијског модела у Јави 5 и општег начина имплементације униката у Јави. Техника овде искоришћена ради у свим верзијама Јаве. Она претпоставља да важе гаранције које даје спецификација Јава језика у вези иницијализације класа и, сходно томе ће радити на свим преводиоцима и виртуалним машинама сагласним са спецификацијама Јава језика.
Унутрашња класа се референцира тек у тренутку када се позове метода getInstance() и не пре тога. Према томе, ово решење је нитно безбедно без захтевања употребе специјалних језичких кострукција (нпр. кључних речи volatile
или synchronized
).
public class Singleton {
// privatni konstruktor zabranjuje instanciranje iz drugih klasa
private Singleton() {}
/**
* SingletonHolder is ucitava na prvi poziv Singleton.getInstance()
* ili prvi pristup promenljivoj SingletonHolder.INSTANCE, nikad pre toga
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
C++
[уреди | уреди извор]//
// unikat koji nije nitno bezbedan
//
class CMySingleton
{
public:
static CMySingleton& Instance()
{
static CMySingleton singleton;
return singleton;
}
// ostale nestaticke funkcije clanice
private:
CMySingleton() {} // privatni konstruktor
~CMySingleton() {}
CMySingleton(const CMySingleton&); // zabranjujemo konstruktor kopije
CMySingleton& operator=(const CMySingleton&); // zabranjujemo dodeljivanje
};
// Datoteka zaglavlja (.h)
//
// Nitno bezbedni unikat. Verzija 1.
//
class Singleton
{
private:
static Singleton *_instance;
static void createInstance();
Singleton() {}
~Singleton() {}
Singleton(const Singleton &);
Singleton & operator=(const Singleton &);
public:
static Singleton &getInstance();
};
// Datoteka izvornog koda (.cpp)
//
// Ovo zaglavlje je potrebno samo da se pokaze da je potrebno ovako nesto kada
// se radi sa sinhronizacionim primitivama u visenitnom programiranju.
// Zameniti sa odgovorajacim zaglavljem.
#include <SyncronizationPrimitives.h>
// Koristimo anonimni imenski prostor da smanjimo kompleksnost u Singleton.h zaglavlju
namespace
{
Mutex instanceMutex;
};
// Inicijalizacija statickog clana
Singleton* Singleton::_instance = NULL;
// Staticki kreiramo instancu. Usput se uveravamo i da ce destruktor biti pozvan kada aplikacija zavrsi sa radom.
void Singleton::createInstance()
{
static Singleton singletonInstance;
_instance = &singletonInstance;
}
Singleton &getInstance()
{
// Zasticeni deo koda ispod se moze izvrsiti i bez zakljucavanja
// proveravanjem uslova "if (!_instance)" i tako se moze dobiti
// znacajno ubrzanje koda, ali ova tehnika dvostrukog zakljucavanja
// nije pouzdana, i u nekim retkim situacijama, u zavisnosti od
// procesora, kompajlera, nivoa optimizacije i tajminga, program
// moze da se ponasa cudno. Sa druge strane, mozda vam ovo bude dovoljno
// dobro za proveru kada izvagate pouzdanosti i performanse.
if (true)
{
// Pretpostavljamo da imamo nacin zakljucavanja mutex-a u konstruktoru
// i otkljucavanja istog u desktruktoru
ScopedLock guard(instanceMutex);
if (!_instance)
createInstance();
}
return _instance;
}
// Datoteka zaglavlja (.h)
//
// Nitno bezbedni unikat. Verzija 2.
//
// Ova verzija izgleda jako prosta, ali ima par napomena. Prvo, ako
// unikat postoji u nekoj biblioteci, korisnici te biblioteke ce dobiti
// instancu unikata, hteli to oni ili ne.
//
// Drugo je slucaj statickih zavisnosti na datoteke. Pretpostavimo da je
// unikat neki faktor tipa BaseType i da implementira metodu create.
// Sledeca upotreba dovodi do nedefinisanog ponasanja posto je redosled
// inicijalizacije statickih promenljivih datoteka nedeterministicki
//
// 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();
};
// Datoteka izvornog koda (.cpp)
//
// Inicijalizacija statickih clanova
//
Singleton Singleton::_instance
C#
[уреди | уреди извор]/// <summary>
/// Nitno bezbedna implementacija unikta gde se instanca kreira na prvi poziv
/// </summary>
public sealed class Singleton
{
private static Singleton _instance = new Singleton();
private Singleton() { }
public static Singleton Instance
{
get
{
return _instance;
}
}
}
Руби
[уреди | уреди извор]class Klass
include Singleton
end
Пајтон
[уреди | уреди извор]class Singleton(type):
def __init__(cls, name, bases, dict):
super(Singleton, cls).__init__(name, bases, dict)
cls.instance = None
def __call__(cls, *args, **kw):
if cls.instance is None:
cls.instance = super(Singleton, cls).__call__(*args, **kw)
return cls.instance
class MyClass(object):
__metaclass__ = Singleton
print MyClass()
print MyClass()
Пајтон (коришћењем декоратора)
[уреди | уреди извор]def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...
PHP
[уреди | уреди извор]final class Singleton
{
protected static $_instance;
private function __construct() # ne dozvoljavamo eksplicitni poziv konstruktora! (na primer $v = new Singleton())
{ }
private function __clone() # ne dozvoljavamo kloniranje unikata (na primer $x = clone $v)
{ }
public static function getInstance()
{
if(self::$_instance === NULL) {
self::$_instance = new self();
}
return self::$_instance;
}
}
$instance = Singleton::getInstance();
Недостаци
[уреди | уреди извор]Треба нагласити да овај узорак чини јединично тестирање много тежим[6] јер уноси глобално стање у програм.
Такође треба нагласити да овај узорак смањује потенцијал паралелизације програма јер приступ уникату у вишенитним контекстима мора бити серијализован (нпр. закључавањем).
Заговорници инјекције зависности (енгл. dependency injection) сматрају да је уникат антиузорак због коришћења приватних и статичких метода.
Предложени су и начини разбијања узорка коришћењем техника као што је рефлексија у Јави.[8]
Референце
[уреди | уреди извор]- ^ Alex Miller. Patterns I hate #1: Singleton (језик: енглески), Јул 2007.
- ^ Scott Densmore. Why singletons are evil (језик: енглески), Мај 2004.
- ^ Steve Yegge. Singletons considered stupid (језик: енглески), Септембар 2004.
- ^ J.B. Rainsberger, IBM. Use your singletons wisely (језик: енглески), Јул 2001
- ^ Chris Reath. Singleton I love you, but you're bringing me down Архивирано на сајту Wayback Machine (31. јануар 2010) (језик: енглески), Октобар 2008.
- ^ а б http://googletesting.blogspot.com/2008/11/clean-code-talks-global-state-and.html (језик: енглески)
- ^ Gamma, E, Helm, R, Johnson, R, Vlissides, J: "Design Patterns", page 128. Addison-Wesley, 1995. (језик: енглески)
- ^ Yohan Liyanage Breaking the Singleton (језик: енглески), 21. септембар 2009.