Singleton Design - PHP - Hogyan használjuk?

img

A Singleton Pattern egy Design minta, amit azért használunk, hogy egy adott osztályt ne tudjunk bizonyos esetekben újra inicializálni. Ez annyit jelent, hogy ha egy osztályt implementálunk Singletonként, akkor ez az osztály nem fog kétszer létezni, tehát újra inicializáláskor már a meglévő osztályt fogjuk visszakapni, így nem fog dupla vagy akár tripla helyet elfoglalni. A legjobb alkalmazási területei például beállítások kezelése vagy a már említett Registry.

Minden webaplikációnak vannak olyan területei, amiket többször több helyen is (például: beépülő modulokban, külső könyvtárakban) használunk. Ha minden alkalommal, mikor szükségünk van egy osztályra újrainicializáljuk, rengeteg felesleges memóriát fogunk felemészteni. Hogy ezt kivédjük, biztosnak kell lennünk, hogy az osztályunk csak egyszer létezik és globálisan elérhető. Elsőként le kell tiltanunk, hogy az osztály kivülről inicializálható legyen, így a konstruktort priváttá kell tennünk.

class MintaOsztaly
{
    private function __construct(){}
}

Ezzel tudjuk megakadályozni, hogy az osztályunkat újra lehessen inicializálni. Ez jelenleg nagy problémának tűnhet, mert nem tudod kivülről meghívni az osztályt, hiszen a __construct metódushoz csak az osztály maga vagy a gyerekei férhetnek hozzá. Kívülről meghívhatatlan.

A megoldás egyszerű. Mivel az osztály kivülről meghívhatatlan, de belülről meghívható, így készítenünk kell egy metódust, ami meghívja az adott osztályt. Ez statikus kell, hogy legyen, mivel így érhetjük csak el inicializálás nélkül is a metódust. Tehát készítünk egy static function getInstance() függvényt. Ennek a metódusnak lesz a feladata, hogy elkészítsen egyetlen példányt az osztályunkból és csak ezzel az egyetlen példánnyal térjen vissza minden esetben.

Tehát a fenti példát bővítsük ki. Bevezetünk egy változót is, ennek is privát hozzáférésünek kell lenni és statikusnak, ebben fogjuk tárolni az egyedet.

class MintaOsztaly
{
    private static $instance = null;

    public static function getInstance()
    {
        if (null === self::$instance) {
            self::$instance = new MintaOsztaly();
        }

        return self::$instance;
    }

    private function __construct(){}

}

Így már csak egyetlen egyede lehet az osztályunknak, ami nagyon szuper.

$minta=MintaOsztaly::getInstance();

De ez még mindig nem akadályoz meg minket, hogy duplikáljuk a létrehozott osztályt a __clone() metódussal.

Tehát bővíteni kell még egy kicsit a kódot:

class MintaOsztaly
{
    [....]

    private function __clone(){}

}

Ezzel a klónozást is letiltottuk. Így az osztályunkból minden esetben csak egyetlen egy darabot tudunk inicializálni. Ráadásul előny, hogy a kódunkon belül bármikor meghívható. Ez legtöbbször jól jön, ha mondjuk a globális beállításokat akarjuk letárolni és hozzáférhetővé tenni mondjuk egy plugin vagy egy külső library számára.

A legtöbb cofig általában néhány soros, kevés adatot tartalmaz, adatbázis kapcsolathoz szükséges beállítások, stb. De ha ezt minden alkalommal újra és újra betöltjük, elég sok memóriát fog felemészteni. Közhely, de "sok kicsi sokra megy".

Első ránézésre a Singleton valami olyasmi, mint egy globális változó. De ez nem igaz. A globális változó egy változó, ami mindenhonnan elérhető a programon belül, míg a Singleton egy Design Pattern, vagyis egy programozási minta. Nincs konstruktor a Singleton létrehozásához a PHP nyelvben, ezzel szemben ott van a $GLOBALS a globális változók részére. Tehát a globális változó egy szerkezet, ami lehetővé teszi, hogy a fejlesztő a változó tartalmához bárhonnan hozzáférhetővé váljon. Ezzel szemben a Singleton Pattern csak egy segítség, hogy az objektumod egyszer inicializálható, mégis mindenhonnan elérhető legyen.

// Ez Hibát fog dobni, mivel az osztály konstruktora privát
$minta = new MintaOsztaly();

// Tehát ahhoz, hogy megkapjunk egy példányt a getInstance()-t kell meghívnunk
$minta = MintaOsztaly::getInstance();

// Klónozáskor is hibát fog dobni mivel ez is privát
$minta2 = clone $minta;

// De a getInstance() bármikor újra meghívható, hiszen vagy létrehoz egy példányt vagy visszatér az eredetivel.
$minta3 = MintaOsztaly::getInstance();

Az első sor meghívásával a következő üzenetet kapjuk:

Fatal error: Call to private MintaOsztaly::__construct() from invalid context in /home/Leoamros/public_html/Singleton/index.php on line 32

A második sor meghívása előtt az $instance változó értéke null a meghívás után:

var_dump($minta);
print_r($minta);

Visszakapjuk az osztályt:

object(MintaOsztaly)#1 (0) { }
 MintaOsztaly Object ( )

A minta klónozásakor ismét hibát fogunk kapni:

Fatal error: Call to private MintaOsztaly::__clone() from context '' in /home/Leoamros/public_html/Singleton/index.php on line 40

A minta3 ugyanazzal az osztállyal fog visszatérni:

object(MintaOsztaly)#1 (0) { }
 MintaOsztaly Object ( )

Tehát látjuk, hogy a Resource mindkét esetben #1. Ha ezt egy normál class meghíváskor csináljuk, tehát nem Singleton Patternnel, akkor a klónozás után így fog kinézni:

object(MintaOsztaly2)#1 (0) { }
 MintaOsztaly2 Object ( )
 object(MintaOsztaly2)#2 (0) { }
 MintaOsztaly2 Object ( )

Tehát egy második példányt hozott létre. Ezzel növelve a memóriában az elfoglalt helyet.

A Singleton Pattern működése

Végezetül akkor összegezzük, hogyan is működik pontosan a Singleton

privát konstruktor és privát klónozás - megakadályozzuk, hogy kivülről hívható legyen az osztály

privát instance = null - megkadályozzuk, hogy kivülről módosítható legyen az egyed, és alapértelmezetten üresen hozzuk létre

getInstace() hívása - a getInstace() felelős azért, hogy csak egyetlen egyed létezzen, csak neki van hozzáférése a privát konstruktorhoz

  • megvizsgálja, hogy létezik-e már létrehozott példány
  • ha nem létezik ($instance==null), akkor meghívja az osztályt (new Class)
  • az $instance az osztály lesz maga
  • ha létezik, vagy létre lett hozva az egyed, visszatér vele

Ezáltal biztosítja, hogy minden alkalommal csak egyetlen példány létezik.

A Singleton és GLOBALS közötti különbség

Feljebb említettem, hogy bár kivülről ugyanúgy működnek, mégsem ugyanaz. Mégis mi a különbség? Nagyon egyszerű! A Singleton Pattern nélkül az osztályunkat többször is inicializálhatjuk globális változókba:

$mintaTomb=array();

global $mintaTomb;

$mintaTomb[0]=new mintaOsztaly();
$mintaTomb[1]=new MintaOsztaly();

Eredmény:

array(2) {
    [0]=> object(MintaOsztaly2)#1 (0) { }
    [1]=> object(MintaOsztaly2)#2 (0) { }
}

Array (
    [0] => MintaOsztaly2 Object ( )
    [1] => MintaOsztaly2 Object ( )
)

Tehát Singleton nélkül az osztályunk újra és újra inicializálható. A Resource ID a 0. tömbelemben még #1, az 1. tömbelemben már #2. Olyan, mintha klónoznánk.

Remélem segített ez a kis leírás valakinek. Okosan használva a Singleton is egy nagyon jó megoldás, annak ellenére, hogy sokan nem ajánlják. Erre legtöbbször a legegyszerűbb kifogás, hogy a profik tudják, mit és mikor inicializálnak, így nem fogják feleslegesen újra meghívni az osztályt.