Feb 01 2007
Singleton
Filed under Java,Ogólne,PHP by Łukasz Dywicki
Singleton jest chyba pierwszym z “wzorców projektowych” jaki wszyscy poznaliśmy. Prosty w implementacji, jeszcze łatwiejszy w użyciu, ale pociągający za sobą stos negatywnych konsekwencji.
W poszukiwaniu informacji i zdań o singletonie w polskim internecie trafiłem na Wikipedię, gdzie znalazłem zdanie, które podsumowało to czym jest tenże “wzorzec”:
Singleton jest też uznawany za antywzorzec, gdyż często jest tylko eufemizmem dla zmiennej globalnej.
W książce “Refaktoryzacja do wzorców projektowych” padają kolejne dwa ważne zdania:
Singleton to przede wszystkim wykorzystanie mechanizmów chroniących języka programowania do ochrony tylko jednego aspektu: jednostkowości.
Prawdziwym problemem z singletonami polega na tym, że są doskonałą wymówką, aby lekko potraktować kwestię widoczności obiektów.
Główną przesłanką do stosowania singletonu jest to by mieć tylko jedną instancję danej klasy, ale czy nie da się załatwić tego w inny sposób? Oczywiście, że da się. Problemem jest tylko to by poświęcić więcej czasu na projekt, po prostu pomyśleć. W chwili gdy mamy połączenie z bazą danych i tak jest ono tworzona w jakimś miejscu, zwykle jest to jakiś kontroler bądź inny przyczółek w którym koncentruje się “władza”. Jest to miejsce w którym i tak musimy daną instancję skonfigurować. Co to znaczy? Ano to, że cały ten singleton i tak nie jest autonomiczny, ponieważ jest zależny od czynników zewnętrznych. Idąc dalej – czy nie lepiej by było pchnąć stworzony już obiekt do tych miejsc gdzie będzie potrzebny?
Oczywiście, ktoś powie, że wydłużą się definicje metod i tak dalej. Fakt, to może być problem, ponieważ metody, które przyjmują powyżej 4 argumentów stają się “ciężkie”. Ciężkie również pod względem zapamiętania ich wywoływania. Nie można tu również zapominać o tym, że nie musi być przymusu przekazywania za każdym razem danej instancji. Mamy do dyspozycji konstruktory jak również kompozycję, ogólnie pojętą fazę “konfiguracji”. Zaletą unikania singletonów jest bardzo duży plus – obiekt nie jest widoczny tam gdzie nie powinien. Jest to znacznie lepsze podejście tym bardziej, że globalnie dostępny obiekt lubi kusić, kusić łatwością dostępu. Zawsze gdy myślisz o tym by użyć singletonu myśl o nim jak o zmiennej globalnej, bo tym w gruncie rzeczy się wkrótce stanie, elementem, który będzie powodował, że kod stanie się nieczytelny a kontrola dostępu do instancji wbrew pozorom, zamiast trudniejsza, jeszcze łatwiejsza.
Wydajność
Za singletonem może przemawiać to, że powoduje on wzrost wydajności poprzez oszczędność pamięci (mniej stworzonych obiektów, mniej zużytej pamięci). Niestety często pomijanym faktem jest to, że zysk z takiego singletonu i tak jest niezauważalny, a w samym PHP wywołania statyczne są znacznie wolniejsze od tradycyjnych “dynamicznych” metod.
Singleton a testy jednostkowe
Singleton znacznie utrudnia testowanie kodu, ponieważ uniemożliwia on pełne rozbicie komponentów. Elastyczniejsze okazuje się użycie metod, które pozwalają “wstrzyknąć” określony obiekt, który jest w danej chwili potrzebny. Elementy, które są przykryte singletonem są niewymienne, muszą być stałe. W chwili gdy singleton tworzy sam instancję klasy pochodnej zaczynamy mieć do czynienia w gruncie rzeczy z abstract factory (fabryką abstrakcyjną) bądź factory method (metodą fabryczną) co w efekcie można nazwać bardzo prosto – przerostem formy nad treścią.
Raz stworzony i zaszyty wewnątrz singletonu obiekt jest świętą krową, nie da się go zastąpić mimo, że na potrzeby testów potrzebny jest leciutki obiekt, który tylko dostarczy odpowiednie dane bądź zachowa informacje o wywołaniach (stub, mock object). Co gorsza, w chwili gdy mamy do czynienia z typowym “kontekstem”, który tworzy w prywatnym konstruktorze kolejne usługi nie mamy możliwości ich podmiany na czas testów. Osobiście spotkałem się z sytuacją, w której testy kończyły się niepowodzeniem, ponieważ w konstruktorze kontekstu było tworzone okno aplikacji.
Połączenie singleton+singleton a Java
Warto zapamiętać, że odwołania pomiędzy singletonami w Javie nie mogą występować w fazie inicjowania obiektów. To znaczy że kod, w którym konstruktor singletonu A wymaga singletonu B, a ten z kolei singletonu A to dostaniemy odpowiedni wyjątek. Przykładowo: kontekst tworzy nowe połączenie, które pobiera instancję jeszcze niestworzonego kontekstu by dobrać się do konfiguracji.
Wnioski
Zanim zdecydujesz się na użycie singletonu przeczytaj trzy cytowane zdania i zastanów się czy rzeczywiście nie ma alternatywy? Nie ma sytuacji w której singleton jest niezbędny, zawsze można znaleźć coś lepszego niż zmienna globalna do przekazywania instancji. Pamiętaj singleton jest wypaczeniem idei programowania obiektowego na potrzeby leniwych programistów.
No to może jakiś przykład jak sobie poradzić najlepiej bez singletona? Ja korzystam z niego w przypadku klasy do obsługi bazy danych – jest to dla mnie niesamowicie wygodne, proste, przejrzyste i intuicyjne. Wydaje mi się, że przerost formy nad treścią byłby wtedy, gdybym próbował właśnie to obejść i stosował dwa razy dłuższy kod po to tylko, aby pisać “poprawnie”. Jeśli singleton jest dla mnie wygodny i wydajny, to wolę pisać z jego użyciem, aniżeli móc chwalić się innym, że nie użyłem tej “nowej zmiennej superglobalnej”.
@wk: to, że sobie go raz użyłeś w aplikacji to spoko, ale jak ktoś używa tego np. do konfiguracji czy innych rzeczy jeszcze to jest to już wg mnie głupie, no chyba, że umie uzasadnić dlaczego akurat taki sposób wybrał, ale większość niestety nie potrafi i do tych osób jest kierowany ten tekst, by MYŚLAŁY co robią ;]
Dwa razy dłuższy kod? Gdzie niestosowanie singletonu objawia się dwa razy dłuższym kodem?
Przecież przekazywanie połączenia do DAO można załatwić poprzez konstruktor i odpowiednio skonstruowaną factory method, powiedzmy w kontrolerze.
Co do wydajności samego singletonu – to tak jak pisałem, bujda.
Proste testy wykonane przy pomocy xdebuga:
http://img.dywicki.pl/singleton1.jpg
http://img.dywicki.pl/nonsingleton1.jpg
Kod robi dosłownie to samo:
singleton: http://phpfi.com/199382
non singleton: http://phpfi.com/199381
Proszę, zwróć uwagę na to, że obiekt był tworzony za każdym razem w pętli i było to dwa szybsze niż nadzwyczaj wydajny singleton.
Na dobrą sprawę nigdy nie korzystałem na poważnie z singletonów i w ogóle preferując rozwagę w stosowaniu wzorców projektowych. Jak sobie z tym poradzić? Najprostszy możliwy sposób: masz obiekty trzech głównych klas, wrzucasz je do trzech zmiennych globalnych i wszędzie dajesz “global”. Efekt jest dokładnie ten sam. Uprzedzając ewentualne pytania – jak ktoś myśli podczas programowania, to od razu wygospodaruje sobie miejsce na inicjację tych trzech obiektów i nie będzie problemu w stylu “a gdzie mi się to będzie tworzyć?”
“global” do programowania obiektowego ma się tak jak pięść do nosa. ;)
Wydaje mi się, że dla osób, które nadużywają “global” przejście do języka, który uniemożliwia wykorzystywanie takich mechanizmów (vide Java) będzie ciężkie a jedynym sposobem na obronę będzie właśnie nieszczęsny singleton..
Napiszę odnośnie posta pierwszego i połączenia z bazą danych. Bez singletona też się da. Sam mam tak zrobione. Po prostu resource do bazy trzymaj w zmiennej prywatnej klasy. A generalnie odwołuj się do tej samej instancji obiektu. Co to szkodzi :)
Jeśli używamy Javy można jeszcze zastąpić singletona za pomocą kontenera IoC ;]
Inversion of Control ludzie!
Przeciez juz przeczkolak wie ze singleton to anty-pattern.
Testowac go nie sposob, powoduje coupling, etc.
Czasami jest jeszcze stosowany we framework-ach ale mocno ukryty przed uzytkownikiem (razem z ThreadLocal).
Tu pare linkow do IoC:
http://www.springframework.org/
http://code.google.com/p/google-guice/
http://www.picocontainer.org/3.1+Container+Overview
Blagam was i niech mnie nikt nie przekonuje, ze warto uzywac singletownow.
[…] Często singleton jest obwiniany o zastępstwo zmiennych globalnych, jedynie w bardziej przystępnej formie – idea jest ta sama. Szczególnie uwidacznia się to, gdy stosujemy ten wzorzec zbyt często. O tym problemie możemy przeczytać na blogu Splatcha. […]
nie lepiej, zamiast singletonu, wykorzystać klasę statyczną? Problem z wydajnością w PHP został naprawiony w ostatnich aktualizacjach, więc ja nie widzę przeciwwskazań, żeby do niektórych zastosowań wykorzystać statyczną klasę. Piszę w oparciu o Zend Framework, on też używa tak singletonów jak i staticów.
[…] Temat testów jednostkowych nie pojawiał się na tym blogu tak często jak PHP czy JAXB, jakkolwiek temat ten poruszałem w 2 notach – o testach oraz o singletonie. […]
singletonów nie da się testować ? eeee… no da się, bez problemu. Jakieś 2 lata temu to faktycznie to był problem. Ale obecnie, w dobie JEasyTest, czy jMockit ? (zainteresowany ? – patrz http://kaczanowscy.pl/tomek/2008-01/untestable-code-introduction)
pozdrawiam
Tomek
Singletony tak jak pisałem “znacznie utrudnia testowanie”. Nie napisałem, że się nie da, bo na upartego da radę przetestować wszystko. Singleton reprezentujący statyczny kontekst jest trudny, zwłaszcza gdy zawiera on więcej obiektów niż powinien – próba testu wiąże się właśnie ze stubowaniem całego kontekstu. Pół biedy jeśli singleton jest inicjowany leniwie (czytaj w metodzie zwracającej instancję) a nie statycznie (blok bądź pole statyczne) – wówczas nawet mock nie poradzi sobie z tym, ponieważ zanim zacznie działać JEasyTest singleton już wywali jakiś NullPointerException bądź połączy się z bazą danych.
Mówię, że najgorsze co może być do testów to singleton łamiący prawo Demeter. :-)
przekazac do konstruktora kazdego nowego obiektu (wykorzystującego baze danych) referencje polaczenia. Moze kopiowac referencje do zmiennej prywatnej. Co najlepsze mozna miec kilka konstruktorow co daje elastycznosc gdy nie bedzie istnialo polaczenie z baza.
Wiecej zalet niz singleton, pelna kontrola
Widzę że mimo upływu czasu temat singletonu jest wciąż otwarty, niestety Dependency Injection w PHP rozwija się wolno i wciąż nie zyskuje zasłużonej popularności.
A co jeżeli mamy obiekt logiki biznesowej która jest singletonem? Nie ma on stanu i jest tylko zbiorem metod do obsługi obiektów z bazy? Czy takie użycie singletona jest poprawne? A co jeżeli w tych metodach używamy odwołań do innych obiektów logiki(które też są singletonamo) aby wykonać inną operację? – to chyba łamie prawo Demeter.
Hej PAT,
Nie wiem co się kryje pod pojęciem “logika biznesowa która jest singletonem”. Logika, która jest niezmienna nie wymaga singletonu, a zmiennym tym bardziej, ale żeby o tym rozmawiać i jednoznacznie stwierdzić czy singleton jest w tej sytuacji konieczny trzeba by pokusić się o jakiś przykład.
Prawda jest taka, że większość przypadków użycia singletonu wynika z lenistwa ponieważ jak pisałem singleton jest tym samym czym globalna, statyczna zmienna.