Unikat (projektni uzorak)

S Vikipedije, slobodne enciklopedije

U softverskom inženjerstvu, unikat (engl. singleton) je projektni uzorak kojim se obezbeđuje da klasa ima samo jednu instancu. Ovo je korisno kada je tačno jedan objekat potreban da koordiniše akcije u sistemu. Ovaj koncept se nekad generalizuje na cele sisteme koji rade efikasnije kada postoji samo jedan objekat, ili na ograničavanje instanciranja, ne na jedan, nego na tačno određen broj objekata (npr. pet). Ovaj projektni uzorak neki smatraju i antiuzorkom jer se previše koristi, unosi nepotrebna ograničenja kada samo jedna instanca klasa uopšte ni nije potrebna i unosi globalno stanje u program. [1] [2] [3] [4] [5] [6]

Opšte upotrebe[uredi | uredi izvor]

  • Projektni uzorci apstraktne fabrike, graditelja i prototipa mogu da koriste unikat u njihovim implementacijama.
  • Objekti fasade su najčešće unikati jer je uglavnom potreban samo jedan objekat fasade.
  • Objekti koji čuvaju stanje su često unikati.
  • Unikati se uglavnom preferiraju u odnosu na globalne promenljive jer:
    • Ne zagađuju globalni imenski prostor (a u jezicima koji imaju imenske prostore, prostor u kome su sadržani) nepotrebnim promenljivama.[7]
    • Omogućavaju lenju alokaciju i inicijalizaciju dok globalne promenljive u mnogim jezicima uvek zauzimaju resurse, korišćene ili ne.

Dijagram klasa[uredi | uredi izvor]

Implementacija[uredi | uredi izvor]

Implementacija unikat projektnog uzorka mora da zadovolji uslov da se ne može napraviti više od jedne instance jedne iste klase, kao i uslov da pristup toj instanci bude globalno dostupan. Potreban je i mehanizam pristupa članicama unikatne klase bez kreiranja nove instance ukoliko ona već ne postoji. Ukoliko instanca već postoji, potrebno je samo vratiti referencu na nju. Da bi se osiguralo da se objekat ne može instancirati na neki drugi način, obično se konstruktor pravi da bude zaštićen (ne i privatan, jer ponovna upotreba ili jedinični testovi mogu tražiti pristup konstruktoru). Primetiti razliku između obične statičke instance klase i unikata: iako se unikat može implementirati kao statička instanca, takođe se može i lenjo inicijalizovati, tj. tako da ne zahteva nikakve memorijske ili druge resurse dok oni ne budu stvarno potrebni. Druga bitna razlika je da statičke klase ne mogu da implementiraju interfejs, osim ako taj interfejs nije običan marker. Tako da, ako klasa mora da izloži neki ugovor kroz interfejs, mora se koristiti unikat projektni uzorak, a ne statička instanca.

Unikat projektni uzorak se mora pažljivo praviti u aplikacijama koje koriste više niti. Ukoliko dve niti u isto vreme pokušaju da pozovu metodu instanciranja unikatne klase koja još ne postoji, obe moraju da provere postojanje instance unikata, a onda samo jedna od te dve niti treba da kreira instancu. Ako programski jezik ima mogućnosti konkurentne obrade, ovaj metod treba biti tako napisan da se koristi operacija uzajamnog isključivanja (engl. mutually exclusive). Klasično rešenje je da se koristi uzajamno isključivanje nad klasom koja ukazuje da je objekat unikata instanciran.

Primeri implementacija[uredi | uredi izvor]

Java[uredi | uredi izvor]

Sva rešenja izneta ovde za programski jezik Javu su nitno bezbedna, ali se razlikuju u podržanim verzijama jezika i po tome da li imaju lenju inicijalizaciju.

Standardni prosti način[uredi | uredi izvor]

Ovo rešenje je nitno bezbedno bez dodatnih jezičkih konstrukcija, ali ne obezbeđuje lenju inicjalizaciju. Promenljiva INSTANCE se kreira čim se klasa Singleton instancira(jezik: engleski). Ovo može biti mnogo pre nego što se pozove metoda getInstance(), na primer kada se pozove neka druga statička metoda klase. Ako lenja inicijalizacija nije potrebna ili je svejedno kada će se instanca kreirati, ili vaša klasa nema drugih statičkih članica ili metoda koji mogu slučajno da kreiraju instancu klase, onda se može koristiti sledeće rešenje:

 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;
   }
 }

Rešenje Bila Puga[uredi | uredi izvor]

Istraživač Bil Pug sa Univerziteta u Merilendu je istraživao unikat projektni uzorak u Javi i probleme sa njim. Pugovi napori na prevazilaženju problema dvostrukog ispitivanja pri zaključavanju (engl. Double-checked locking) su doveli do promene memorijskog modela u Javi 5 i opšteg načina implementacije unikata u Javi. Tehnika ovde iskorišćena radi u svim verzijama Jave. Ona pretpostavlja da važe garancije koje daje specifikacija Java jezika u vezi inicijalizacije klasa i, shodno tome će raditi na svim prevodiocima i virtualnim mašinama saglasnim sa specifikacijama Java jezika.

Unutrašnja klasa se referencira tek u trenutku kada se pozove metoda getInstance() i ne pre toga. Prema tome, ovo rešenje je nitno bezbedno bez zahtevanja upotrebe specijalnih jezičkih kostrukcija (npr. ključnih reči volatile ili 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++[uredi | uredi izvor]

//
// 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#[uredi | uredi izvor]

/// <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;
        }
    }
}

Rubi[uredi | uredi izvor]

class Klass
 include Singleton
end

Pajton[uredi | uredi izvor]

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()

Pajton (korišćenjem dekoratora)[uredi | uredi izvor]

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
    ...


PHP[uredi | uredi izvor]

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();

Nedostaci[uredi | uredi izvor]

Treba naglasiti da ovaj uzorak čini jedinično testiranje mnogo težim[6] jer unosi globalno stanje u program.

Takođe treba naglasiti da ovaj uzorak smanjuje potencijal paralelizacije programa jer pristup unikatu u višenitnim kontekstima mora biti serijalizovan (npr. zaključavanjem).

Zagovornici injekcije zavisnosti (engl. dependency injection) smatraju da je unikat antiuzorak zbog korišćenja privatnih i statičkih metoda.

Predloženi su i načini razbijanja uzorka korišćenjem tehnika kao što je refleksija u Javi.[8]


Reference[uredi | uredi izvor]

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

Spoljašnje veze[uredi | uredi izvor]