Singleton - dobro czy zło?

1

Cześć.

Mam takie pytanie - czy singleton jest czasami konieczny?

Mam sytuację, iż pisze aplikację, gdzie loguję się do bazy danych na samym starcie i owe połączenie powinno trwać przez cały okres trwania programu.
W tym przypadku wiele osób radzi mi zastosować singletona i owszem jest to rozwiązanie bardzo proste... aż za proste.

Zastosowanie "nowoczesnego" programowania strukturalnego mnie nie zadowala raczej, aczkolwiek nie wiem, czy jest sens tworzyć to inaczej. Ja wykonałem to w ten sposób, iż w pewnym miejscu tworzę odpowiedni obiekt realizujący połączenie / rozłączenie i wszelkie zapytania do bazy danych, a następnie ten obiekt przekazuję do innych klas jeśli jest taka potrzeba (zastosowałem asocjację).

W ten jednak sposób w kilku klasach mam np:
NazwaKlasy NazwaObiektu = ReferencjaDoObiektuTejSamejKlasy.

i wtedy te wszystkie zmienne operują na jednym obiekcie, dzięki czemu program napisany jest obiektowo.

Z drugiej jednak strony gdyby zastosować tutaj wzorzec singleton, to aplikacja nie bawiłaby się w przekazywanie obiektów, jednak używałaby metod statycznych, co według mnie jest niezbyt eleganckie.

Co Wy byście radzili w podobnej sytuacji?

0

Tak, to jest normalne i w takim przypadku uzasadnione. Pamiętaj jednak by singletona tworzyć przed utworzeniem innych wątków oraz by był tylko do odczytu (ewentualnie synchronizacja, ale staraj się jej unikać jak możesz).

0

Ok dzięki bardzo :)

2

Poczytaj o Dependency Injection/ Inversion of Control (wstrzykiwanie zależności). Zwykle polega to na tym, że jest specjalny kontener beanów (czy jak to tam nazwać) i kontener buduje sobie graf zależności między beanami i buduje ten graf od dołu (tzn od liści). Potem możesz sobie te beany wstrzyknąć do kodu jeśli masz dostępny kontekst z załadowanym tym grafem. Grafy można łączyć, nawet dynamicznie.

Singletonów używam rzadko, a jeśli już to korzystam z tego rozwiązania: http://en.wikipedia.org/wiki/Singleton_pattern#The_solution_of_Bill_Pugh (leniwa inicjalizacja, brak narzutów na synchronizację, w miarę czytelny kod).

0

Jeśli Cię boli Singleton to możesz go zamienić na Rejestr (który jest Singletonem, ale jednym do wszystkiego).
Dzięki temu usuwasz zależność od klasy, pozostawiając tylko zależność od interfejsu (dodatkowo możesz zastosować Fabryki).

Przykład dla PHP:
http://develturk.com/2009/04/18/php5-object-registry-implementation-a-registry-design-pattern/

1

Prawie wszystko, co jest statyczne, a mogłoby nie być jest z perspektywy programowania obiektowego złe.

Każda aplikacja ma coś, co nazywa się composition root. W PHP będzie to plik index.php, w C/C++ funkcja main. Jeżeli coś ma żyć od uruchomienia aplikacji do jej zakończenia to zwyczajnie utwórz obiekt potrzebnej klasy i przekaż go gdzie trzeba. Zdecydowanie może to ułatwić, jak już wspomniał Wibowit, DI / kontener IoC.

Wiem, że programiści PHP nie piszą testów, ale wyobraź sobie jak utrudnione byłoby testowanie klasy, która jest singletonem i ma jakikolwiek stan, który będzie wpływać na wykonanie niezależnych od siebie testów.

Rozwiązanie przedstawione przez vpiotr, czyli rejestr / service locator też nie jest idealne, choć to pewien krok w lepszą stronę. Minusem takiego rozwiązania jest np. to, że nie wiemy jakie zależności ma klasa (co musi być wcześniej zarejestrowane) dopóki nie 1. przeczytamy dokumentacji, 2. nie uruchomimy kodu, 3. nie przeczytamy źródła tej klasy. Typowe DI wskaże nam już to na poziomie kompilacji, bo np. nie podaliśmy zależności w konstruktorze.

0

@Rev: W Rejestrze nie zastanawiasz się nad zależnościami, po prostu pobierasz obiekt, zakładając że już istnieje.
A nawet jeśli chcesz zastosować "lazy construction" to możesz zaimplementować "podaj-lub-utworz-i-podaj-obiekt", metodę Rejestru, która wywoła odpowiednią fabrykę jeśli obiekt nie jest dostępny.

DI w postaci XML obecne w Javie to dla mnie przegięcie - głównie ze względu na debugowanie, ale również podpowiedzi (których chyba nie ma w XML?) i to że wolę poprawiać kod niż dane... (formatowanie, komentarze itd).

Co do testów w PHP to chyba mamy jakąś inną perspektywę, bo mi się wydawało, że xUnit jest wymogiem w językach skryptowych, gdzie kod się nie wywali dopóki nie zostanie wywołany (i może zawierać totalne bzdury). Skąd taki pomysł że tam nie ma unit testów (jeśli o to Ci chodziło)?

1

Jak singleton przeszkadza w robieniu testów?

Jeśli singleton jest mutowalny to przy robieniu testów musisz go co każdy test resetować do stanu pierwotnego - kolejność wykonywania testów nie może mieć wpływu na wyniki testów. Obecność mutowalnego singletonu wyklucza także często możliwość równoległego odpalenia wielu testów naraz (np mamy wielordzeniowego CPU i chcemu sobie przyspieszyć testy). Trudno też zmockować singletony - trzeba uciekać się do jakichś PowerMocków czy tym podobnych sztuczek z classloaderami.

0

Zgadzam się z Wibowitem i Revem.
Dodam od siebie przestrogę przez service locatorem: http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx

vpiotr napisał(a):

DI w postaci XML obecne w Javie to dla mnie przegięcie - głównie ze względu na debugowanie, ale również podpowiedzi (których chyba nie ma w XML?) i to że wolę poprawiać kod niż dane... (formatowanie, komentarze itd).

Niestety nie jestem Javowcem, ale nie można użyć bindowania z poziomu kodu lub auto bindowania ? W .NET większość kontenerków DI to udostępnia, a po XML się sięga, gdy potrzeba lazy loadingu.

0
kubanvip napisał(a):

Zgadzam się z Wibowitem i Revem.
Dodam od siebie przestrogę przez service locatorem: http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx

vpiotr napisał(a):

DI w postaci XML obecne w Javie to dla mnie przegięcie - głównie ze względu na debugowanie, ale również podpowiedzi (których chyba nie ma w XML?) i to że wolę poprawiać kod niż dane... (formatowanie, komentarze itd).

Niestety nie jestem Javowcem, ale nie można użyć bindowania z poziomu kodu lub auto bindowania ? W .NET większość kontenerków DI to udostępnia, a po XML się sięga, gdy potrzeba lazy loadingu.

Link ciekawy, oczywiście Service Locator / Register skoro jest Singletonem to tak samo jak on ma stan.
To nie jest super, oczywiście, ale dzięki temu osiągamy wysoki poziom separacji.
Są oczywiście też inne stopnie DI - jeszcze bardziej od-kodowany jest XML w Javie, a trochę bardziej ludzkie są:

  • contructor injection
  • setter injection

Jeśli chodzi o XML-a to też nie jestem Javovcem, ale zobacz na to:
http://www.vogella.com/articles/SpringDependencyInjection/article.html
http://marcelog.github.com/articles/ding_xml_yaml_di_dependency_injection.html
http://www.zalas.eu/managing-object-creation-in-php-with-the-symfony2-dependency-injection-component

W skrócie chodzi o to, że cały kod inicjujący, wiążący obiekty zamieniasz na definicje w XML-u.

Singletony są najczęściej krytykowanym wzorcem, ale jakoś się ich używa - niestety zawsze jest tak, że w końcu dochodzisz do obiektów zewnętrznych dla systemu i często musisz mieć do tego jeden obiekt. Ale oczywiście warto tego unikać.

0
vpiotr napisał(a):

W skrócie chodzi o to, że cały kod inicjujący, wiążący obiekty zamieniasz na definicje w XML-u.

To był właśnie pierwszy sposób konfigurowania wiązania interfejsów z implementacjami.
Później zauważono, że bardzo dużo niepotrzebnego słowotoku trzeba użyć do opisu + XML jest kruchy, łatwo popełnić błędy przy tworzeniu, wiec pojawiła się opcja konfiguracji wiązania w kodzie.
Ponieważ wiązanie każdego interfejsu do obiektu go implementującego to dużo roboty pojawiły się mechanizmy automatycznej rejestracji - np przyjmujemy konwencję, że interfejsy "IXXX" są implementowane przez klasy "XXX".

Metoda wstrzykiwania zależności (constructor injection, setter injection, method injection) jest zupełnie niezależna od sposobu rejestrowania powiązań dla kontenera.

vpiotr napisał(a):

Singletony są najczęściej krytykowanym wzorcem, ale jakoś się ich używa - niestety zawsze jest tak, że w końcu dochodzisz do obiektów zewnętrznych dla systemu i często musisz mieć do tego jeden obiekt. Ale oczywiście warto tego unikać.

Chodzi Ci o to, żeby wszystkie obiekty korzystające z niego odwoływały się do tej samej instancji ?
W kontenerze możesz zarządzać cyklem życia obiektu. W tym przypadku rejestrujesz powiązanie "in singleton scope" i wtedy wszędzie będą odwołania do tej samej instancji.

0

Zarówno Spring, Google Guice jak i masa pomnijeszych frameworków udostępnia możliwość automatycznego wiązania za pomocą adnotacji. W Springu np oznacza się klasę adnotacją @Configuration, na metodach fabrykujących lub na całej tej klasie można dodać @Lazy. Spring oprócz rozpoznawania po klasach może rozpoznawać po nazwach (jak i specjalnych tagach, a może nawet i więcej sposobów na rozpoznawanie obsługuje), np w konstruktor przyjmuje parametry Klasa(String dupa1, String dupa2) a w naszej fabryce beanów mamy:

String dupa1() {
  return "dupa1";
}
String dupa2() {
  return "dupa2";
}

To wtedy Spring się zorientuje co gdzie trzeba wstrzyknąć.

Domyślnie każdy @Bean w Springu jest singletonem (dla danego kontekstu oczywiście, nie dla całej aplikacji), ale można mu np ustawić adnotację @Prototype i będziemy zawsze dostawać nową instancję.

1

Nie lubię singletonów.
To takie zaprzeczenie obiektowości.

Nie lubię ich pisać. Wydają mi się zbędnym, nadmiarowym kodem, podobnie jak trywialne funkcje set i get do każdego pola.
Nie lubię ich używać już istniejących. Są nienaturalne.
Jeśli potrzebujemy jednego obiektu, to tworzymy jeden obiekt. Po co ograniczać klasę do jednej instancji?

Co Wy byście radzili w podobnej sytuacji?
Raczej tak jak opisałeś: tworzyłbym jeden obiekt i w miarę potrzeby go przekazywał. Choć w idealnym przypadku użycie obiektu powinno ograniczać się do jednej klasy, i nie byłoby potrzeby przekazywania.

Prawie wszystko, co jest statyczne, a mogłoby nie być jest z perspektywy programowania obiektowego złe.
Mam odwrotne zdanie. To co może być statyczne, powinno być statyczne. Ale przez "może być statyczne" mam na myśli: bezstanowe, i bez skutków ubocznych. Np. funkcja odwracająca podanego stringa spokojnie może być statyczna, ale już łączenie z bazą danych - nie.

Typowy singleton to udawanie metod statycznych aby tylko z nazwy nie używać metod statycznych, więc jest złe.

0

@Azarien:
Wygląda jakbyś programował w czymś innym...
Singletony nie są stosowane po to żeby mieć jeden obiekt.
Są po to o czym wspomniałeś - aby uniknąć przekazywania w kółko tych samych obiektów (np. połączenia do bazy danych, obiektu logującego, danych aktualnego użytkownika) w całej aplikacji.

W szczególnym przypadku sigleton implementowany jest jako "multiton", gdzie masz wiele instancji, ale sens ten sam:
http://en.wikipedia.org/wiki/Multiton_pattern

A teraz zadanie na dziś:

  • zaimplementować generator liczb losowych, używany w całej aplikacji, z zastosowaniem boost::variate_generator (lub innego obiektowego generatora liczb pseudolosowych) bez użycia obrzydliwego singletona (lokalny obiekt statyczny w funkcji to też singleton).
0

Raczej tak jak opisałeś: tworzyłbym jeden obiekt i w miarę potrzeby go przekazywał. Choć w idealnym przypadku użycie obiektu powinno ograniczać się do jednej klasy, i nie byłoby potrzeby przekazywania.

Ten obiekt który chcę przekazać wędruje pomiędzy formatkami, więc niestety nie da się tego chyba zrobić tylko w jednej klasie :(

Na razie zrobiłem kod z singletonem, aczkolwiek nie podoba mi się to. Najprawdopodobniej zmienię to na zwykły obiekt, który przekazuję pomiędzy klasami.

Jednak bardzo dziękuję wszystkim zza opisane tu rady. Sporo można się dowiedzieć :)

1

@vpiotr:
Do C++ są jakieś frameworki do wstrzykiwania zależności, a do Javy i .NETa jest multum. A skoro mamy wstrzykiwanie zależności i budowanie obiektów w jednym miejscu, tzn konfiguracji beanów, to nie trzeba przepychać zależności wprost. Każda klasa może przyjmować tylko takie parametry, które sama wykorzystuje i nie musi przyjmować referencji do singletonów, żeby je przepchać na dół, bo robi to za nią kontener.

0
programistaonanista napisał(a):

Raczej tak jak opisałeś: tworzyłbym jeden obiekt i w miarę potrzeby go przekazywał. Choć w idealnym przypadku użycie obiektu powinno ograniczać się do jednej klasy, i nie byłoby potrzeby przekazywania.

Ten obiekt który chcę przekazać wędruje pomiędzy formatkami, więc niestety nie da się tego chyba zrobić tylko w jednej klasie :(

Na razie zrobiłem kod z singletonem, aczkolwiek nie podoba mi się to. Najprawdopodobniej zmienię to na zwykły obiekt, który przekazuję pomiędzy klasami.

Jednak bardzo dziękuję wszystkim zza opisane tu rady. Sporo można się dowiedzieć :)

Jeżeli cały czas problemem jest to co opisałeś w pierwszym poście, czyli praca z bazą danych, to czy wzorzec repozytorium nie spełni twoich oczekiwań ?
Przekazujesz go jako zależność do klas które muszą operować na zewnętrznych danych. Jak różnorodność obsługiwanych danych rośnie, tworzysz różne repozytoria i/lub nakładasz serwisy.

0

Jeżeli cały czas problemem jest to co opisałeś w pierwszym poście, czyli praca z bazą danych, to czy wzorzec repozytorium nie spełni twoich oczekiwań ?
Przekazujesz go jako zależność do klas które muszą operować na zewnętrznych danych. Jak różnorodność obsługiwanych danych rośnie, tworzysz różne repozytoria i/lub nakładasz serwisy.

Chyba nie rozumiem tego wzorca bo nie za bardzo widzę w nim różnicę pomiędzy obiektem stworzonym na podstawie klasy, a obiektem stworzonym na podstawie interfejsu.
Tzn. rozumiem zasadę i logikę, jednak nie widzę w jaki sposób by to rozwiązało problem przekazywania obiektu.

1
programistaonanista napisał(a):

Jeżeli cały czas problemem jest to co opisałeś w pierwszym poście, czyli praca z bazą danych, to czy wzorzec repozytorium nie spełni twoich oczekiwań ?
Przekazujesz go jako zależność do klas które muszą operować na zewnętrznych danych. Jak różnorodność obsługiwanych danych rośnie, tworzysz różne repozytoria i/lub nakładasz serwisy.

Chyba nie rozumiem tego wzorca bo nie za bardzo widzę w nim różnicę pomiędzy obiektem stworzonym na podstawie klasy, a obiektem stworzonym na podstawie interfejsu.
Tzn. rozumiem zasadę i logikę, jednak nie widzę w jaki sposób by to rozwiązało problem przekazywania obiektu.

Interfejs usuwa zależność od hierarchii klas. Nie dostajesz i nie korzystasz z informacji o dziedziczeniu. Nie musisz zatem wciągać klasy bazowej i wszystkich innych od których ona zależy. Importujesz tylko moduł interfejsu (w C++ klasy abstrakcyjnej).

0

onanista:
Ogólnie wstrzykiwanie zależności polega na oddelegowaniu tworzenia obiektów do kontenera/ kontekstu. Na samym początku w aplikacji zwykle powstaje jakiś tam graf obiektów i możesz go od razu wyciągnąć z kontenera. Czasem jednak zachodzi potrzeba tworzenia obiektów w czasie działania programu i jeżeli jest taka klasa, która potrzebuje nowych obiektów, to powinno się jej wstrzyknąć referencję do tego kontenera/ kontekstu i wtedy ona będzie sobie te obiekty w miarę potrzeby z niego wyciągać.

0
Wibowit napisał(a):

onanista:
Ogólnie wstrzykiwanie zależności polega na oddelegowaniu tworzenia obiektów do kontenera/ kontekstu. Na samym początku w aplikacji zwykle powstaje jakiś tam graf obiektów i możesz go od razu wyciągnąć z kontenera. Czasem jednak zachodzi potrzeba tworzenia obiektów w czasie działania programu i jeżeli jest taka klasa, która potrzebuje nowych obiektów, to powinno się jej wstrzyknąć referencję do tego kontenera/ kontekstu i wtedy ona będzie sobie te obiekty w miarę potrzeby z niego wyciągać.

Przekazywanie klasom bezpośredniej referencji do kontenera nie jest dobrym rozwiązaniem - to powrót do service locatora, tyle że nie dostępnego poprzez singleton, a wstrzykiwanego. Lepiej wstrzykiwać fabryki.
Poza typowymi wadami service locatora dochodzi tutaj problem twardego powiązania modułów z kontenerem. Idealnie moduły nie powinny być w ogóle świadome kontenera. Kontener powinien siedzieć w osobnym module, bootstrapperze. Ten moduł dopiero ma referencje na wszystkie wymagane moduły i w nim rozwiązywane są zależności.

0

No wstrzykiwanie kontekstu rozwiązuje przynajmniej jeden problem - nie ma statycznego mutowalnego stanu i nie ma przekazywania referencji w dół. Wstrzykiwanie fabryk daje jeszcze uniezależnienie od kontenera.

1

zawsze mozna zrobic klase host, ktora posiada wszystkie zmienne, obiekty itp ktore mialyby byc w tym jednym singletonie - widzialem juz takie rozwiazania.

1 użytkowników online, w tym zalogowanych: 0, gości: 1