Wzorce projektowe są udokumentowanymi i sprawdzonymi sposobami na rozwiązanie często spotykanych problemów projektowych. Dotyczą więc problemów, z którymi już się prawdopodobnie zetknąłeś i nad którymi spędziłeś nieco czasu, zanim znalazłeś takie bądź inne rozwiązanie.
Mimo iż wzorce projektowe jako takie funkcjonują w
informatyce od dawna, jednak po raz pierwszy zostały one zebrane i nazwane
przez tzw. Bandę Czworga (Gang of Four), czyli Ericha Gammy,
Richarda Helma, Ralpha Johnsonsa i Johna Vlissidesa.
Opublikowali oni książkę pod tytułem \”Wzorce projektowe, elementy oprogramowania
obiektowego wielokrotnego użytku\”. W Polsce została ona wydana w 2005
roku przez WNT. Banda Czworga podzieliła wzorce na trzy grupy i tak też przyjęło się je
klasyfikować:
- wzorce konstrukcyjne – są to metody pozyskiwania obiektów klas, zamiast
ich bezpośredniego tworzenia (np. za pomocą operatora new), - wzorce strukturalne – opisują sposoby łączenia poszczególnych obiektów
w większe, współdziałające ze sobą struktury, - wzorce czynnościowe – są metodami opisującymi sposoby kontroli przepływu
danych w złożonych aplikacjach.
Wzorce projektowe wykorzystywane są przy programowaniu obiektowym
i intensywnie korzystają z technik obiektowych.
Przykłady tu zaprezentowane będą zbudowane w oparciu o PHP5 (starsze
wersje niestety miały zbyt ubogą obiektowość) oraz Javę. Jednak jeśli znasz
dobrze inny obiektowy język programowania, nie powinieneś mieć problemów
z ich zastosowaniem.
Singleton
Z artykułu tego dowiesz się, czym jest wzorzec singleton – prawdopodobnie
najprostszy i chyba najbardziej znany ze wzorców projektowych. Należy on
do grupy wzorców konstrukcyjnych.
Czym jest singleton? Czy nie zdarzyło ci się, że w swojej aplikacji tworzyłeś
wiele razy identyczny (robiący to samo, działający na tych samych danych etc.)
obiekt klasy – np. nawiązującej połączenie z bazą (zakładając, że jest to zawsze
– w ramach aplikacji – dokładnie jedna i ta sama baza)? Można co prawda
stworzyć taki obiekt raz i odwoływać się do niego np. za pośrednictwem
zmiennej na niego wskazującej – oto prosty przykład:
class Test {
public static $dbConnection;
}
Test::$dbConnection = new DB(); //wystarczy tylko raz
Test::$dbConnection->query(‚SELECT * FROM test;’);
Java:
public class Test {
static public DB db = new DB();
}
/*
Dostęp do obiektu
Test.db.query(\'SELECT * FROM test;\');
*/
Jednak nie zabezpiecza cię to przed możliwością niepotrzebnego ponownego
tworzenia obiektu klasy DB (np. przez pomyłkę czy przez innego programistę,
nieświadomego istnienia takiej zmiennej).
Zastanów się chwilkę, jaki jest
warunek konieczny, aby możliwe było jawne utworzenie obiektu danej klasy?
Tak – publiczny konstruktor. Klasa taka jak poniżej:
class Test {
private function __construct() {
}
}
class Test {
private Test() {
}
}
nie pozwoli na jawne stworzenie swojej instancji poprzez użycie operatora
new. Próba utworzenia obiektu podobnej klasy zaowocuje wyświetleniem
komunikatu błędu (w przypadku Javy będzie to błąd czasu kompilacji):
Fatal error: Call to private Test2::__construct() from
context\' in F:\Pages\DB.php on line 2
Main.java:4: Test() has private access in Test
Test t = new Test();
^
Dlaczego więc nie miałbyś wykorzystać tego faktu i po prostu zakazać
jawnego tworzenia obiektów klasy, której chcesz posiadać tylko jeden obiekt
dla całej aplikacji?
Jeśli zastanawiasz się, co ci to da, bo przecież pozbywasz się w ten
sposób możliwości korzystania z całej funkcjonalności klasy, odpowiedź
kryje się w metodach i polach statycznych. Prywatny konstruktor w połączeniu
z metodą i polem statycznym oferuje to, czego właśnie potrzebujesz
(pamiętaj, że wewnątrz klasy masz swobodny dostęp do jej pól i metod
prywatnych, a więc i do konstruktora). Oto jak powinien być zbudowany
singleton:
class Singleton {
private static $objInstance = null;
private $intValue = null;
private function __construct() {
$this->intValue = rand(1, 100);
}
public static function getInstance() {
if (is_null(self::$objInstance)) {
self::$objInstance = new self();
}
return self::$objInstance;
}
public function getValue() {
return $this->intValue;
}
}
import java.util.Random;
public class Singleton {
private static Singleton instance;
private int value;
private Singleton() {
Random generator = new Random();
value = generator.nextInt();
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public int getValue() {
return value;
}
}
W powyższym przykładzie celowo w konstruktorze generowana jest liczba
losowa. Dzięki temu możesz sprawdzić, że korzystając z sinegletonu, masz
do czynienia zawsze z tym samym obiektem klasy.
Jak to działa? Do pobrania
obiektu klasy musisz użyć statycznej metody {stala}getInstance(){/stala} – pamiętaj,
że nie zadziała w tym przypadku próba stworzenia obiektu za pomocą
operatora new:
$objS = Singleton::getInstance();
Singleton s = Singleton.getInstance();
Powoduje to wywołanie statycznej metody, która ma za zadanie
w razie konieczności stworzenie odpowiedniego obiektu, a następnie
zwrócenie go. Pamiętaj, że klasa ma dostęp do własnych metod prywatnych,
dzięki temu w wewnętrznej metodzie getInstance klasy działa
operator new. Zauważ też, że nowy obiekt tworzony jest tylko wówczas,
gdy nie został uprzednio przypisany do statycznego pola klasy, mającego
za zadanie przechowywanie takiego obiektu. Jeśli okazuje się, że obiekt
ten został już uprzednio utworzony, metoda getInstance po prostu
go zwraca (przypomnij sobie, że pola statyczne są wspólne dla wszystkich
obiektów).
W przypadku normalnej klasy każde stworzenie instancji powodowałoby
nowe wygenerowanie liczby w konstruktorze. Zobacz, jak to będzie wyglądało
w przypadku singletonu:
echo Singleton::getInstance()->getValue().\'
\';
echo Singleton::getInstance()->getValue().\'
\';
echo Singleton::getInstance()->getValue().\'
\';
System.out.println(Singleton.getInstance().getValue());
System.out.println(Singleton.getInstance().getValue());
System.out.println(Singleton.getInstance().getValue());
Za każdym razem wyświetlona zostanie ta sama wartość. Jest ona generowana
tylko raz, podczas tworzenia obiektu singletona, czyli podczas pierwszego
wywołania metody {stala}getInstance(){/stala}.
I to już wszystko.
Zakończenie
Singeton wykorzystywany jest dość często, jednak zanim go użyjesz,
zastanów się kilka razy, czy na pewno go potrzebujesz oraz czy nie wystąpi
w przyszłości konieczność utworzenia wielu obiektów danej klasy.
Może się
bowiem zdarzyć, że z powodu źle zaprojektowanej aplikacji będzie to niemożliwe.
Pamiętaj także o jeszcze jednym – prywatny konstruktor w singletonie
skutecznie zamyka ci drogę do dziedziczenia w innych klasach po klasie
będącej singletonem.
Singleton w pigułce
- Tworzymy klasę z prywatnym konstruktorem.
- Tworzymy statyczną prywatną zmienną do przechowywania obiektu
naszej klasy. - Tworzymy statyczną publiczną metodę o nazwie np. {stala}getInstance(){/stala},
- W metodzie {stala}getInstance(){/stala} sprawdzamy czy statyczna prywatna
zmienna przechowuje już obiekt klasy, jeśli nie – tworzymy go - Zwracamy w metodzie {stala}getInstance(){/stala} wartość statycznej prywatnej
zmiennej przechowującej obiekt singletona. - Pamiętaj – singleton odpowiada za dostarczenie obiektu danej klasy,
zapewniając jednocześnie, że utworzony będzie tylko i wyłącznie jeden
obiekt tej klasy.