Singleton – alternatywne podejście
Pewnego dnia, jadąc w nocy na rowerze zamyśliłem się na temat klas, funkcji i obiektów. Przemyślenia te zawiodły mnie do pewnych ciekawych wniosków.
Nie będę ich tutaj w tej chwili przytaczał, natomiast wpadł mi do głowy pewien ciekawy pomysł jak zrealizować wzorzec singleton w alternatywny sposób, opierając się na funkcjach anonimowych.
Czym jest wzorzec Singleton ? Pokrótce mówiąc, stosujemy go jeżeli a) chcemy mieć tylko jedną instancję obiektu, b) jeżeli chcemy aby nasz obiekt był globalnie dostępny.
Jednym z wzorców, który korzysta z singletona jest rejestr, większość z Was pewnie używała go w Zendzie gdzie jest nagminnie wykorzystywany. Dla przypomnienia:
//inicjalizacja singletona $oReg = Zend_Registry::getInstance(); //ustawienie zmiennej w rejestrze $oReg->jakis_klucz = 'jakas wartosc'; //pobranie echo $oReg->jakis_klucz;
Jedną z rzeczy, którą uważam za złą w tej implementacji od strony filozoficznej jest to, że klasy generalnie definiujemy po to by tworzyć wiele instancji danego obiektu. W tym przypadku potrzebujemy tylko jeden egzemplarz, stosując klasy trzeba używać więc dodatkowych sztuczek. Natomiast zamiast tworzyć klasę i bawić się w Singletona z klasą rejestru stworzyć taką oto zgrabną funkcję:
function Registry($key, $value = null){
static $reg;
if($value === null){
return $reg[$key];
} else {
$reg[$key] = $value;
}
}
//ustawianie
Registry('a','b');
Registry('d','e');
//pobieranie
echo Registry('a');
echo Registry('d');
Jak widać implementacja jest prosta jak budowa czołgu t-52 i realizuje wszystkie założenia rejestru – jedna instancja i globalna dostępność elementów które przechowuje.
Przejdźmy dalej i spróbujmy zaimplementować Singletona w podobny sposób, ale już jako konkretny obiekt z metodami.
Klasyczne podejście wygląda mniej więcej tak:
class My_Singleton {
protected static $_instance = null;
private $_prywatna1 = 'aaa';
private $_prywatna2 = 'bbb';
protected __construct(){
}
public getInstance(){
if(self::$_instance === null){
self::$_instance = new self;
}
return self::$_instance;
}
public function method1(){
echo $this->_prywatna1.' <br/>';
$this->_prywatna2 = 'ccc';
}
public function method2(){
echo $this->_prywatna2.' <br/>';
}
}
My_Singleton::getInstance()->method1();
My_Singleton::getInstance()->method2();
Alternatywne podejście:
class Proxy {
function __call($name, $arguments) {
if(is_callable($this->$name)){
return call_user_func_array($this->$name, $arguments);
}
}
}
function Singleton() {
static $self;
//konstruktor pol
if($self == null) {
$self = new stdClass();
$self->_prywatna1 = 'aaa';
$self->_prywatna2 = 'bbb';
}
//konstruktor metod
static $oObj;
if($oObj == null) {
$oObj = new Proxy();
$oObj->method1 = function() use ($self) {
echo $self->_prywatna1.' <br/>';
$self->_prywatna2 = 'ccc';
};
$oObj->method2 = function() use ($self) {
echo $self->_prywatna2.' <br/>';
};
}
return $oObj;
}
Singleton()->method1();
Singleton()->method2();
Technika przedstawiona powyżej jest bardzo podobna do programowania opartego na prototypach, z jakim mamy np do czynienia w JavaScript’cie. Jedyną różnicą w ciele metod „metoda1″ i „metoda2″ jest zastąpienie zmiennej $this, zmienną $self, spowodowane tym, że $this jest zastrzeżoną nazwą zmiennej. Przykład spełnia założenia Singletona – jedna instancja i dostępność w kontekście globalnym. Jedyną wadą tego rozwiązania jest to, że nie da się dziedziczyć po takim obiekcie, mam wrażenie jednak, że nie jest to koniecznie potrzebne.
Ciekawy jestem Waszego zdania o tej technice, zapraszam do komentowania
P.S przykłady działają tylko w PHP 5.3.x
Related posts:
Twoje rozwiązanie wydaje się być nieco przekombinowane. Nie od dziś wiadomo, że metody magiczne (tutaj __call) nie należą do demonów szybkości. Nie wiem jak jest z wydajnością anonimowych funkcji.
Niemniej jednak, jest to fajny przykład możliwości jakie daje PHP 5.3.
Generalnie jest to tylko eksperyment, nie sprawdzałem jak szybko ten kod działa. Natomiast ten sposób implementacji Singletona, jest mam wrażenie dość odkrywczy. Podobnie tworzy się Singletony w języku Scala. Ogólnie jest tam kilka ciekawych rzeczy w modelu obiektowym, które chętnie bym widział w php, dla zainteresowanych polecam wpis o modelu obiektowym Scali dla programistów Javy.
Skoro mowa o eksperymentach, to co jakiś czas wracam do tematu klas częściowych (C#) i ich implementacji w PHP. Niestety wszystkie moje pomysły rozbijają się o metody magiczne i/lub refleksję, przez co cały mechanizm jest mało wydajny. Za jakiś czas opiszę to na blogu, może ktoś będzie miał lepszy pomysł.
Chętnie poczytam o tym. Zdaje mi się, że klasy częściowe są podobną koncepcją do trait’ów i graft’ów ?
Graft-y wyglądają mi bardziej na metody rozszerzeń (extension methods) niż na klasy częściowe.
Wiadomo czy i kiedy będzie to w PHP zaimplementowane?
Grafty generalnie polegają na tym, że możesz do jednej klasy zaimportować wybrane metody z innej klasy. Co do traitów, to są w trunku i mają być w nowej wersji php-a (5.4?) ale kiedy to nastąpi tego nawet najstarsi górale nie wiedzą ;P