Singleton, Registry map
Jan 17
Ostatnio przeglądałem forum.php.pl i znalazłem kilka postów, które dały mi do zrozumienia, że część społeczności nie potrafi używać, lub w ogóle nie wie czym jest singleton. Jako, że staram się pomagać bliskim mi w mojej branży ludziom o ile pozwala mi na to czas i ochota, postaram się wyjaśnić prostymi słowami czym jest i jak uzywać singletona. W artykule poruszone jest wiele problemów, np dziedziczenie songletona wraz z przedstawianymi rezultatami działań (unikatowe identyfikatory instacji klas). Przedstawiłem wzorzec registry, zapraszam do lektury.
1. Czym jest singleton – teoria
Singleton to pojedyncza instacja klasy, mówiąc szerzej: dostępna w obrębie całej aplikacji w postaci jednego egemplarza, aby nie tworzyć cały czas nowej kopii obiektu operatorem new i korzystać z nej tak, jak ze zwykłej klasy (a nie z metod statycznych). Cały singleton opiera się na tym, że klasa przechowuje instancję samej siebie w prywatnym statycznym atrybucie, który jest tworzony i pobieranie w metodzie statycznej zastępującej konstruktor. No właśnie… jak zablokować nasz konstruktor, aby nie można było posłużyć się klasą poprzez operator z zewnątrz metody uruchamiającej ową instancję. Jeżeli klasa nie będzie dziedziczona, metodzie konstrukcyjnej nadajemy prawa dostępu na „private‿, lub jeżeli chcemy dziedziczyć klasę „protected‿.
Dobrze zablokowaliśmy dostęp do klasy, teraz trzeba jakoś przechować obiekt i wywołać go. Do przechowania instancji klasy będzie służył jej wbudowany mechanizm polegający na zapisie egzemplarza do własnego prywatnego/chronionego atrybutu i wywołaniu przez metodę statyczną klasy zwracającą ową instancję. Zapoznamy się ze słowem kluczowym „instanceof‿, który sprawdza, czy obiekt jest instancją danej klasy.
2. Piszemy singleton niedziedziczony i dziedziczony – praktyka
Teorię znamy, czas na przyklad. Napiszemy klasę HelloWorld, która wykona songletona sama siebie:
[php]< ?php
final class HelloWorld
{
private static $_oInstance = null;
private function __construct()
{
}
public static function Instance()
{
if(!self::$_oInstance instanceof self)
self::$_oInstance = new self;
return self::$_oInstance;
}
public function SayHello()
{
echo 'Hello World!';
}
}
$oHello = HelloWorld::Instance();
$oHello->SayHello();
?>[/php]
Aby udowodnić, że operujemy na wzorcu singleton i dane zawarte w atrybutach nie zmieniają się, dopiszemy do metody konstrukcyjnej generowanie hashu, który zostanie zwrócony razem z napisem Hello World!. Przykład pierwszy udowodni, że korzystamy z tego samego egzemplarza klasy zwracając ten sam identyfikator (wykonany jednorazowo). Przykladem wykonania takiego identyfikatora może być na przyklad połączenie z bazą danych.
[php]< ?php
final class HelloWorld
{
private static $_oInstance = null;
private $_sUniqueIdentyficator = null;
private function __construct()
{
$this->_sUniqueIdentyficator = md5( sha1( time() * rand(1, 999) ) );
}
public static function Instance()
{
if(!self::$_oInstance instanceof self)
self::$_oInstance = new self;
return self::$_oInstance;
}
public function SayHello()
{
echo ‘Hello World! ID: ‘ . $this->_sUniqueIdentyficator;
}
}
$oHello1 = HelloWorld::Instance();
$oHello1->SayHello();
$oHello2 = HelloWorld::Instance();
$oHello2->SayHello();
$oHello3 = HelloWorld::Instance();
$oHello3->SayHello();
?>[/php]
Aby udowodnić, że identyfikator zmienia się bez użycia wzorca singleton, napisałem przykład bez uzycia singletona. Poniższy przykład zwróci różne klucze.
[php]< ?php
final class HelloWorld
{
private $_sUniqueIdentyficator = null;
public function __construct()
{
$this->_sUniqueIdentyficator = md5( sha1( time() * rand(1, 999) ) );
}
public function SayHello()
{
echo ‘Hello World! ID: ‘ . $this->_sUniqueIdentyficator;
}
}
$oHello1 = new HelloWorld;
$oHello1->SayHello();
$oHello2 = new HelloWorld;
$oHello2->SayHello();
$oHello3 = new HelloWorld;
$oHello3->SayHello();
?>[/php]
Dobrze, teraz zajmiemy się dziedziczeniem metod oraz instancji singleton. Aby to zrobić, klasa zamiast słów kluyczowych private musi otrzymać uprawnienia protected. Stwórzmy pattern, który udowodni nam taką możliwość:
[php]< ?php
class HelloWorld
{
protected static $_oInstance = null;
protected $_sUniqueIdentyficator = null;
protected function __construct()
{
$this->_sUniqueIdentyficator = md5( sha1( time() * rand(1, 999) ) );
}
public static function Instance()
{
if(!self::$_oInstance instanceof self)
self::$_oInstance = new self;
return self::$_oInstance;
}
public function SayHello()
{
echo ‘Hello World! ID: ‘ . $this->_sUniqueIdentyficator;
}
}
class HelloWorldChildren extends HelloWorld
{
// …
}
$oHello1 = HelloWorld::Instance();
$oHello1->SayHello();
$oHello2 = HelloWorld::Instance();
$oHello2->SayHello();
$oHello3 = HelloWorldChildren::Instance();
$oHello3->SayHello();
$oHello4 = HelloWorldChildren::Instance();
$oHello4->SayHello();
?>[/php]
Jak widzimy dzieci otrzymały instncję wcześniej stworzonego rodzica. UWAGA: Jeżeli najpierw zadeklarujemy dziecko, wówczas rodzic nie będzie miał dostępu do jego instancji.
3. Registy – teoria
Registry to nic innego jak zbiór instancji klas w klasie. Mapa registry to zapisywanie instancji klasy pod pewnym identyfikatorem, po czym wybieranie DOKſADNIE takiej samej instacji jaka została zapisana w dowolnym momencie działania aplikacji. Co należy zrobić? Klasa registy mus posiadać atrybut statyczny prywatny, który będzie przechowywał tablicę o wzorze: Identyfikator => Obiekt klasy. Metoda Registry() będzie pobierała instancję zapisaną pod pewnym kluczem, a metoda Register() będzie rejestowała obiekty pod określonym identyfikatorem.
4. Zastosowanie Registry Map – praktyka:
Założenia znamy, są bardzo łatwe, zabierzmy się za sam kod:
[php]< ?php
final class MyRegistry
{
private static $_aRegistry = array();
public static function Register($sInstanceName, $oInstanceObject)
{
if(!is_object($oInstanceObject))
throw new Exception('Only objects may be in registry map!');
if(is_object(self::$_aRegistry[$sInstanceName]))
throw new Exception('Some object has been registered at name "' . $sInstanceName . '"!');
self::$_aRegistry[$sInstanceName] = $oInstanceObject;
return self::$_aRegistry[$sInstanceName];
}
public static function Registry($sInstanceName)
{
if(!is_object(self::$_aRegistry[$sInstanceName]))
throw new Exception('No object registered at name "' . $sInstanceName . '"!');
return self::$_aRegistry[$sInstanceName];
}
}
final class HelloWorld
{
private $_sUniqueIdentyficator = null;
public function __construct()
{
$this->_sUniqueIdentyficator = md5( sha1( time() * rand(1, 999) ) );
}
public function SayHello()
{
echo ‘Hello World! ID: ‘ . $this->_sUniqueIdentyficator;
}
}
$oHello1 = new HelloWorld;
MyRegistry::Register(‘MyHello1′, $oHello1);
$oHello1->SayHello();
unset($oHello1);
$oHello1 = MyRegistry::Registry(‘MyHello1′);
$oHello1->SayHello();
?>[/php]
W powyższym przykladzie użyliśmy naszej klasy HelloWorld bez użycia singletona. Stworzyliśmy egzemplarz klasy, wywołaliśmy ID, po czym zarejestrowaliśmy w Registry i usunęliśmy obiekt za pomocą unset(). Gdy wyciągamy instancję za pomocą naszego Regostry, okazuje się, że zawiera ona ten sam klucz, co instancja przed usunięciem. Stworzyliśmy coś w rodzaju „klona‿. Zamiast identyfikatora może być to wspomniane połączenie z bazą danych.
To by było na tyle, z tego miejsca pozdrawiam wszystkich programistów oraz ekipę php.pl, 3mcie się :), Athlan

nie rozumiem jednego po jaka cholere wynajdujesz od nowa koło?
Construct?
bez sensu skoro mamy wbudowane __construct
@Acid, a teraz przeczytaj post jeszcze 10 razy (włącznie z tytułem bo dużo mówi) i prześledź czym różni się ta metoda od zwykłego konstruktora… równie dobrze mogłem nazwać tą metodę Run(), albo Singleton(), ale wolałem Construct(), abyś na przyklad Ty dostrzegł różnicę… ale nie :)
Pozdrawiam, Athlan :)
#
$oHello1 = HelloWorld::Construct();
#
$oHello1->SayHello();
a;e uzywasz jej doklanie jako construktora… o to mi konkretnie chodzi
public static function Construct()
{
if(!self::$_oInstance instanceof self)
self::$_oInstance = new self;
return self::$_oInstance;
}
dalej tak uważasz? :)
tak dalej tak uważam, bo właśnie do tego służy konstruktor, aby wykonywać akcje które robić przy uruchamianiu klasy, a Ty doklanie to robisz wywolujesz to Twoje Construct przy kazdym wywołaniu cyzli to samo mozna by bylo zrobic w __construct
testowałeś? czytałeś 10 razy arta, jak Ci kazałem?
Nie.
Metoda Construct() (nazwij ją sobie Run(), będzie Ci łatwiej) wykonuje konstruktor klasy TYLKO I WYſĄCZNIE RAZ, dzięki czemu dostajemy ZAWSZE ten sam egzemplarz klasy. Po stworzeniu instancji samej siebie jest ona zwracana, gdy instancja już istanieje, wówczas nie jest robiona (bo już istnieje prawda). Na tym polega singleton. Pooglądaj obrazki (czytaj: kody źródłowe), które dałąćzyłem w środku tekstu. Zarzuciłem nawet przykład bez użycia singletona (dla takich jak Ciebie), porównaj listingi 2 i 3. Odpal, przeanalizuj co się dzieje a potem pisz :) Jeżeli dalej twardo się zapierasz zapraszam do działu “programowanie obiektowe” na forum.php.pl:
http://forum.php.pl/Programowanie-obiektowe-f49.html
Tam załóż stosowny temat, opisz to, co Ci nie pasuje, ale radzę Ci najpierw przeczytać ten post kolejne 10, a nawet 100 razy.
Pozdrawiam, Athlan :)
Po to nie używa się __construct aby nie dało się utworzyć drugiej instancji klasy ponieważ jak dasz new HelloWorld to sypnie błędem że dostępu do konstruktora nie ma, a jak dasz HelloWorld::Run to wykona sie to raz i przekaże do zmiennej instancje klasy
@Acid, zanim zaczniesz sie wypowiadac na forum publicznym to zastanow sie dobrze, poszperaj w sieci. nie jest głupotą popełnić błąd, ale w nim tkwić.
co do samego posta to oczywiście odkrywczy nie jest, każdy kto chociaż trochę liznął OOP (tak na poważniej) to doskonale zna te wzorce. co prawda dla początkujących ok, ale jak widać po Acidzie i tak nie za bardzo to pomogło ;-)
@Athlan: może użyj w kodzie getInstance() nie będzie się ludziom kojarzyć z konstruktorem, niektórzy widać mają ciężki tryb rozumienia
Poza tym ja się od singletona uwolniłem, paskudny sposób programowania i mnie denerwował :P
@php blog: faktycznie, nie będziemy mieszać w głowach tym, którzy jeszcze się uczą. Metodę za pomocą możemy otrzymać singleton nazwałem Instance.
@sopel, może nic nowego nie wniósł, ale ja w sieci nie spotkałem się z przykładami typu hash, ay udowodnić, przedstawić rezulataty, porównac przykłady. Sam się kiedyś uczyłem i wiem jak jest ciężko, najlepiej tłumaczy się na przykładach, które coś obrazują (zachowanie klasy) dlatego postanowiłem zebrać najważniejsze wiadomości i napisać arta.
Pozdrawiam, Athlan :)
Polecam sprawdzić działanie poniższego przykładu:
http://phpfi.com/200195
Czy rzeczywiście mamy do czynienia z dziedziczeniem? Ano właśnie nie! Odpowiedź można znaleźć wyświetlając stałą __CLASS__ w metodzie Instance();
@splatch, a teraz mała zabawa w klocki :)
http://phpfi.com/200280
var_dump( get_parent_class(‘HelloWorld’) ); // false
var_dump( get_parent_class(‘HelloWorldChildren’) ); // HelloWorld
Pozdrawiam :)
Athlan – piję do tego, że po pierwszym Instance() będziesz miał ustawione pole statyczne w klasie HelloWorld, przez co HelloWorldChildren::Instance() nie będzie zwracało swojej nowej instancji a już stworzoną instancję klasy HelloWorld. Co za tym idzie – po HelloWorldChildren::Instance() nie masz instancji HelloWorldChildren a HelloWorld!
hmm… Co do tego przykładu z singletonem i dziedziczeniem. Niestatycznych metod klasy HelloWorldChildren nie można wywołać, więc użyteczność dziedziczenia singletonu jest znikoma :F
P.S. Pokazywanie błędów wypełnienia pól komentarza wordpressa w operze nie dokońca trybi :P
No niestety zrąbane to jest.
Walczyłem właśnie z możliwością stworzenia klasy Singleton z której dziedziczyłyby wszystkie klasy które chce aby były singletonami. Niestety nie udało mi się… bez sensu, że muszę kilkanaście razy pisać ten sam kod w różnych klasach:/
Wszystko przez to, że self odnosi się do klasy w której został zaimplementowany a nie w której się do niego odwołujemy (w odróżnieniu od $this).
A czy nie powinno się w klasie znaleźć jeszcze:
private function __clone()
{
}
??
Czy może coś nie kumam?
Pozdrawiam
super, ekstra artykuł, idealnie wszystko wyjaśnione wraz z przykładami.
ocena: 11/10 :)