12.05.20203 min

Patrick Assoa AdouSolutions Architect

Wzorzec singleton w PHP

Dowiedz się, jak napisać singleton w PHP, aby wykorzystać jego zalety i zminimalizować wady.

Wzorzec singleton w PHP

Kiedy potrzebujemy jednej instancja obiektu lub określonej klasy, to używamy wzorca singleton. Niektórzy jednak krzywo na niego patrzą i traktują go, jak antywzorzec



Oto niektóre z powodów, dla których singleton jest traktowany, jak antywzorzec:

  • Może on być ukrytym długiem technicznym. Założenie, że potrzebna będzie tylko jedna instancja klasy w czasie życia aplikacji może się później okazać nieprawdziwe. Możliwy skutek to żmudna refaktoryzacja.
  • Wprowadzenie stanu globalnego do aplikacji.
  • Jeśli singleton wprowadza stan globalny i go zmienia, to utrudnia to testy jednostkowe.


Wzorzec singleton nie przestrzega w rzeczywistości zasady pojedynczej odpowiedzialności (ang. Single Responsibility Principle), ponieważ wymagane zachowanie klasy (na przykład, połączenie z bazą danych) jest powiązane z kodem potrzebnym do jednoczesnego występowania pojedynczej instancji.

Aby rozwiązać powyższe problemy, dobrze jest zaprojektować klasę z właściwościami i metodami, które są jej potrzebne, a następnie zaprojektować mechanizm zapewniający, że tylko jedna instancja jest w systemie na raz. Poniżej omówimy kod w PHP, który nam to umożliwi, a w dalszej części artykułu wyjaśnię swoje podejście.


Kod

<?php
function singletonize(\Closure $func)
{
    $singled = new class($func)
    {
        // Hold the class instance.
        private static $instance = null;
        public function __construct($func = null)
        {
            if (self::$instance === null) {
                self::$instance = $func();
            }
            return self::$instance;
        }
        // The singleton decorates the class returned by the closure
        public function __call($method, $args)
        {
            return call_user_func_array([self::$instance, $method], $args);
        }
        private function __clone(){}
        private function __wakeup(){}
    };
    return $singled;
}


Możemy użyć powyższej funkcji:

<?php
class ClassThatMarksDateOfInstantiation
{
    private $dob;
    public function __construct()
    {
        $this->dob = new \DateTimeImmutable;
    }
    public function getDob()
    {
        return $this->dob;
    }
}

$factoryOfDobClass = function () {
    return new ClassThatMarksDateOfInstantiation;
};

$DobSingleton = singletonize($factoryOfDobClass); // -> anonymous class
$dob = new $DobSingleton; // -> new instance of anonymous class
$dob2 = new $DobSingleton; // -> same instance of anonymous class
print_r($dob->getDob() === $dob2->getDob()); // -> 1


Podejście


Funkcja singletonize() przyjmuje domknięcie jako argument, który jest funkcją wytwórczą dla oryginalnej klasy:

$factoryOfDobClass = function () { return new ClassThatMarksDateOfInstantiation; };

Ta klasa nie wie, ile obiektów istnieje w jednym momencie. Robi tylko to, co musi zrobić. Tworzymy nową anonimową klasę, która będzie reprezentować wzorzec singleton i delegować wszystkie wywołania metody do przechwyconej instancji danej klasy. Zwróć uwagę na użycie metody __call() do uzyskania delegacji:

public function __call($method, $args)
{return call_user_func_array([self::$instance, $method], $args);}

Co więcej, nie korzystam teraz z idiomatycznych metod getInstance(), czy instance() zwykle używanych z singletonami. Jeśli za każdym razem będziemy otrzymywać tę samą instancję, to może nie trzeba w kółko powtarzać getInstance()?

Ustawienie metod __clone() oraz wakeup() jako prywatne zapobiega:


Wzorzec singleton oraz wstrzykiwanie zależności

Prawdopodobnie zauważyliście, że singletonize() zwraca anonimową klasę. Oznacza to, że nie możemy użyć oryginalnej klasy bezpośrednio, ponieważ nie zawiera ona logiki zapewniającej istnienie jednego obiektu. Programista wywołujący new dla danej klasy otrzyma wtedy nową instancję. To zdecydowanie nie to, czego chcemy, ale na ratunek przybywa wstrzykiwanie zależności (ang. dependency injection lub DI). 

Mówiąc wprost, DI jest implementacją inwersji sterowania (ang. Inversion of Control). Albo, jak mówi Wikipedia:

[Wstrzykiwanie zależności] polega na przekazywaniu gotowych, utworzonych instancji obiektów udostępniających swoje metody i właściwości obiektom, które z nich korzystają (np. jako parametry konstruktora). Stanowi alternatywę do podejścia, gdzie obiekty tworzą instancję obiektów, z których korzystają np. we własnym konstruktorze.


Podczas wstrzykiwania zależności, zamiast ręcznego tworzenia potrzebnych instancji pozwalasz kontenerowi zależności na dostarczenie konstruktorów klas, ich metod oraz właściwości ze wszystkimi niezbędnymi zależnościami. Oto możliwy przykład użycia:

$this->container->singletonize('DobClass', $this->factoryOfDobClass);
$dob = $this->container->get('DobClass');

Trzymanie się DI pomaga w uniknięciu przypadkowego tworzenia instancji oryginalnej klasy. Nie wspominając już o tym, że DI sprzyja tworzeniu luźnych powiązań, co również może pomóc w testowaniu. 


Podsumowanie

(Anty)wzorzec singleton jest jednym z pierwszych, jakich się jako programiści w ogóle uczymy. Pomimo problemów z naruszeniem zasady pojedynczej odpowiedzialności, dostępem do globalnego stanu lub ukrytym długiem technicznym, to właściwie zrozumiany singleton może być bardzo przydatny.

To, co daje nam singletonize(), to zdolność do oddzielania pilnowania liczby istniejących obiektów od zachowania, a więc i odpowiedzialności klasy. W połączeniu ze wstrzykiwaniem zależności i singletonize(), wzorzec singleton staje się bezpiecznym i wartościowym narzędziem dla inżyniera oprogramowania.

 

Oryginał tekstu w języku angielskim przeczytasz tutaj.

<p>Loading...</p>

Powiązane artykuły

Dziel się wiedzą ze 160 tysiącami naszych czytelników

Zostań autorem Readme

Hitachi Energy

Security Architect

senior

15 000 - 21 000 PLN

Umowa o pracę

Kraków

Praca zdalna 100%

Ważna do 26.02.2022

Bardzo dobrze
Microsoft Azure and/or AWS

Hitachi Energy

Product Development Manager

senior

15 000 - 20 000 PLN

Umowa o pracę

Kraków

Praca zdalna 100%

Ważna do 26.02.2022

Bardzo dobrze
AgileSoftware Development Life Cycle Leadership skills

Simple SA

Java Developer (Mid/Senior)

medium

7 000 - 15 000 PLN

Kontrakt B2BUmowa o pracę

Praca zdalna 100%

Ważna do 26.02.2022

Dobrze
JavaSpringSpring Boot

Asseco Poland S.A.

Administrator / Starszy Administrator Systemów IT

medium

Brak widełek

Kontrakt B2BUmowa o pracę

Praca zdalna 100%

Ważna do 26.02.2022

Dobrze
PostgreSQLBash

Nokia

5G Automation Engineer, IODT

medium

Brak widełek

Umowa o pracę

Wrocław

Praca zdalna 100%

Ważna do 13.03.2022

Divante

Senior Vue.js Developer

senior

15 300 - 23 500 PLN

Kontrakt B2BUmowa o pracę

Wrocław

Praca zdalna 100%

Ważna do 13.03.2022

Dobrze
JavaScriptTypeScriptVue.js

T-Mobile Polska S. A.

Frontend Developer

medium

Brak widełek

Kontrakt B2B

Warszawa

Ważna do 26.02.2022

Bardzo dobrze
ReactReduxNode.js

Commerzbank - Centrum Technologii Cyfrowych w Polsce

Business Expert for Risk Applications

medium

Znamy widełki

Umowa o pracę

Łódź

Ważna do 26.02.2022

Dobrze
SQLMS Office
Początkująco
SAS / R / Python

Commerzbank - Centrum Technologii Cyfrowych w Polsce

Business Expert with German for Risk Analytics

medium

Znamy widełki

Umowa o pracę

Łódź

Ważna do 26.02.2022

Dobrze
MS Office
Początkująco
SQL / VBA / PythonQlik Sense / Qlik View / Arcadia