Pod koniec października tego roku napisałem singletona. Singleton to wzorzec projektowy, który nie pozwala na utworzenie wielu instancji tego samego obiektu. Zamiast tego umożliwia proste odwołanie się do utworzonej instancji obiektu w dowolnym miejscu skryptu, co jest pomocne głównie przy tworzeniu aplikacji opartych na budowie MVC.
1 grudnia Multi Singleton został nominowany do Phpclasses.org Innovation Award, konkursu na innowacje organizowanego co miesiąc przez administrację serwisu Phpclasses.org. W związku z tym postanowiłem napisać o tym wzorcu.
Interfejs
Pierwszym elementem całego skryptu jest interfejs singletona. Nie jest on oczywiście konieczny, ale jeśli chcecie zachować hermetyczność kodu, to może okazać się przydatny. W tym przypadku interfejs zawiera dwie metody:
- getInstance()
- metoda ta będzie odpowiadała za utworzenie lub zwrócenie referencji do istniejącej już instancji obiektu
- __clone
- będzie blokował próby klonowania obiektu
Plik interfejsu to ISingleton.php:
1 2 3 4 5 6 7 | <?php interface ISingleton { public static function getInstance(); public function __clone(); } ?> |
Multi Singleton
Kolejnym plikiem jest Singleton.php. Zawiera on główną klasę singletona, zawierającą zmienną oInstance, która statycznie przechowuje nazwy utworzonych obiektów, implementuje metody przedstawione w interfejsie oraz dodatkowo posiada funkcję __construct() jako finalną z parametrem dostępu ustawionym na protected, aby nie można było utworzyć obiektu poza klasą. Na samym początku pliku jest także dodana funkcja __autoload(), ładująca automatycznie pliki klas. Można ją zmienić, nadpisać swoją lub usunąć, lecz wtedy będzie niepotrzebna zabawa z załączaniem plików.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php class Singleton implements ISingleton { protected static $oInstance = array(); protected final function __construct() {} public static function getInstance() { $className = get_called_class(); if (!isset(self::$oInstance[$className])) self::$oInstance[$className] = new $className(); return self::$oInstance[$className]; } public function __clone() { return false; } } ?> |
Jeżeli zdecydowaliście się nie używać interfejsu, to usuńcie tekst implements ISingleton przy deklaracji klasy Singleton.
Użycie Singletona
Singleton powinien być używany rozsądnie i głównie do dużych, rozbudowanych aplikacji, gdyż w przeciwnym wypadku zaśmiecisz tylko kod i spowolnisz działanie skryptów.
Singleton jest szczególnie potrzebny wtedy, kiedy chcemy utworzyć lub uzyskać łatwy dostęp do parametrów i metod jakiejś klasy głównej.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <?php function __autoload($class) { require_once("{$class}.php"); } class Core extends Singleton { public $tytul = 'To jest tytuł'; public function pokaz_tytul() { print $this->tytul; } } $core = Core::getInstance(); class Design { public function tytul($nowy) { Core::getInstance()->tytul = $nowy; } } $core->pokaz_tytul(); print '<br />'; $dsg = new Design(); $dsg->tytul('Artykuły'); Core::getInstance()->pokaz_tytul(); ?> |
A teraz troszkę tłumaczenia. Najpierw załączamy klasę Singletona do skryptu, następnie tworzymy klasę Core, która jest rozszerzeniem (extends) klasy Singleton. Klasa ta zawiera zmienną tytul oraz metodę pokaz_tytul(), która wyświetla zawartość zmiennej tytul. Kolejnym etapem jest utworzenie globalnej zmiennej core, która będzie przechowywała instancję obiektu Core. Później tworzona jest klasa Design, która zawiera metodę tytul(), zmieniającą wartość zmiennej tytul obiektu core. W ostatnich linijkach tytuł jest zmieniany i wyświetlany.
Powyższy skrypt powinien wyświetlić:
To jest tytuł
Artykuły
Podsumowanie
Mam nadzieję że wszystko jest jasne, chociaż pod koniec zrobiło się dość zawile. Jeśli macie jakieś pytania, to kierujcie je do mnie w komentarzach lub na e-maila.
Klasę znajdziecie także tutaj. Możecie także na mnie zagłosować.
Dziękuję.










Z punktu widzenia modelowania dziedziczenie po singletonie to nie jest zdaje się dobre wyjście…
Hmmm, dlaczego? Przecież daje to taki sam efekt, jakbyś do każdej klasy dodał funkcję getInstance() i zablokował konstruktor.
dziedziczenie oznacza komponenty które są ze sobą w jakiś logiczny sposób powiązane. a tutaj nie ma żadnej takiej relacji. np. Klawiatura, to ogólna klasa dla dziedziczącej po niej klawiaturze qwerty, klawiaturze numerycznej itd. wszystkich potomków łączy wspólna cecha – są urządzeniami IO. W Twoim wykonaniu kilka obiektów dziedziczących Singletona najprawdopodobniej nie będzie zupełnie nic łączyło
p.s.
czemu odpisujesz przed moim postem?:P
Wspólną cechą (logicznym połączeniem) jest tu, moim zdaniem, jednoinstancyjność obiektów dziedziczących
Jak wymyślę do czego to porównać to napiszę.
A co do kolejności komentarzy to tylko chciałem zobaczyć jak wygląda odwrotna kolejność, ale jednak jest “trochę” niewygodna.
To o czym piszesz to nie jest logiczne połączenie. Chyba, że wykażesz, że takie połączenie istnieje dla wszystkich klas dziedziczących. Czyli np. dla klas Database (obsługujących połączenia z bazą danych) i Settings (klasy do obsługi pliku tekstowego z ustawieniami serwera). Dodatkowo, ponieważ w php można dziedziczyć tylko jednokrotnie, Twoje rozwiązanie nie pozwala Singletonom na dziedziczenie po innych klasach.
Przyjrzyj się też, jak singletony implementowane są we frameworkach.
No masz rację. O tym nie pomyślałem. Może uda mi się obejść w jakiś przyjazny sposób dziedziczenie.
Wow, Singleton jest uważany za antywzorzec (choć czasem jest przydatny), ale to co tu jest nie mieści mi się w głowie. Nawet nie wiem od czego zacząć sprostowanie… Może poczytaj najpierw o Service Locator i Dependency Injection. Choć w tym prostym przykładzie do zarządzania instancjami obiektów wystarczy zwykły Registry pattern.
No ok, dependency injection pozwala na użycie alternatywnych klas dla skryptu, a service locator to taki rozszerzony singleton. Wybacz, jeśli wychodzę w tym momencie na durnia, ale mam bardzo małe doświadczenie i trzeba mnie wziąć pod glany i trochę pokierować.
Co do przykłądu, to jest tylko przykład, pokazujący logikę działania klasy.
Wpis jest straszny jeśli chodzi o kod, nie skupiasz się na najważniejszej części, którą chciałeś przekazać. Nie wiem po co sprawdzasz czy dana funkcja czy intefejs jest już zdeklarowany i to zwłaszcza kiedy używamy __autoload(), a skoro już go używamy to po co korzystasz z require_once. No i na końcu jak można używać global ;>
Cały wpis można by umieścić w jednym zdaniu. Zrobiłem dziedziczenie po singletonie i nazwałem to sobie Multi Singleton
Ok, poprawiłem kod.
Co masz na myśli pisząc “nie skupiasz się na najważniejszej części, którą chciałeś przekazać”?
Odnośnie długości wpisu, to staram się pisać tak, żeby każdy wszystko zrozumiał.
Kontynułując za Stormfly:
“Zrobiłem dziedziczenie po singletonie i nazwałem to sobie Multi Singleton”… ” – I nie wiem co oznacza słowo SINGLE”.
Single nie odnosi się do ilości możliwych użyć klasy Singletona, tylko do możliwości stworzenia tylko jednej instancji klasy-dziecka. Multi natomiast odnosi się do możliwości wykorzystania Singletona jako rodzica dla wielu podklas.
Ludzie, co ma dependecy injection do tego? Opisana metoda pozwala zaoszczędzić trochę pisania w prostej aplikacji, w której jest 4 czy 5 singletonów. Frameworki DI są przeznaczone do zupełnie innej klasy problemów. To jakby porównywać TIRa z wózkiem sklepowym. Singletony są oczywiście evil, ale znów, w małym projekcie, w którym występuje kilka obiektów o których wiadomo na 100%, że mogą się pojawić tylko raz, a z jakiegoś powodu nie można użyć pól statycznych, singleton jest idealny. Mniej ideologii, więcej pragmatyki.
A tam zaraz Evil, singleton jest bo jest, przydaje się, np do debugera, oddziela jakby dodatkową warstwę abstrakcji bazy danych. W taki trochę dziwny sposób ale działa. Co do MultiSingletonu, to wybacz ale ja tego nie kupuję. I zgadzam się z Airborn, w dodatku dodam że po to się dziedziczy z jakiejś klasy by też rozszerzyć jej właściwości, a poza tym śmieszny ten multi.
Każdy ma własne zdanie. Jak widać jednym się przydaje, drugim nie. Wszystko zależy od przyzwyczajeń i nawyków.
Nie licz na to Sebastian że ja Ci będę samymi superlatywami jeździł na blogu
Jak masz dostać kubeł zimnej wody na łeb to dostaniesz ;]
Nie liczę na to
Wręcz przeciwnie. Wolę jak ktoś mnie zjedzie, bo wtedy szybciej się nauczę