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

  1. batman pisze:

    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.

  2. Wojciech Soczyński pisze:

    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.

  3. batman pisze:

    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ł.

  4. Wojciech Soczyński pisze:

    Chętnie poczytam o tym. Zdaje mi się, że klasy częściowe są podobną koncepcją do trait’ów i graft’ów ?

  5. batman pisze:

    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?

  6. Wojciech Soczyński pisze:

    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

  1. […] na temat implementacji „odchudzonego” wzorca Singleton bądź też Registry na blogu Wojtka Soczyńskiego.Tags: php, staticZostaw komentarzImię (wymagane)Email (wymagane)Strona Dozwolone tagi: <a […]

  2. […] Jeśli chodzi o inne zastosowania, możecie także poczytać na temat implementacji „odchudzonego” wzorca Singleton bądź też Registry na blogu Wojtka Soczyńskiego. […]

Leave a Reply

Informuj mnie o odpowiedziach poprzez e-mail. Możesz również subskrybować wpis bez zostawiania komentarza.