Уникат (пројектни узорак)

У софтверском инжењерству, уникат (енгл. 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;
   }
 }
//
// 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
/// <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:
    ...


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]


Референце

уреди
  1. ^ Alex Miller. Patterns I hate #1: Singleton (језик: енглески), Јул 2007.
  2. ^ Scott Densmore. Why singletons are evil (језик: енглески), Мај 2004.
  3. ^ Steve Yegge. Singletons considered stupid (језик: енглески), Септембар 2004.
  4. ^ J.B. Rainsberger, IBM. Use your singletons wisely (језик: енглески), Јул 2001
  5. ^ Chris Reath. Singleton I love you, but you're bringing me down Архивирано на сајту Wayback Machine (31. јануар 2010) (језик: енглески), Октобар 2008.
  6. ^ а б http://googletesting.blogspot.com/2008/11/clean-code-talks-global-state-and.html (језик: енглески)
  7. ^ Gamma, E, Helm, R, Johnson, R, Vlissides, J: "Design Patterns", page 128. Addison-Wesley, 1995. (језик: енглески)
  8. ^ Yohan Liyanage Breaking the Singleton (језик: енглески), 21. септембар 2009.

Спољашње везе

уреди