Kiedy jest Singleton odpowiedni? [Zamknięte]

Odpowiedzi:

32

Dwie główne krytyki Singletonów dzielą się na dwa obozy z tego, co zaobserwowałem:

  1. Singletony są niewłaściwie wykorzystywane i nadużywane przez mniej zdolnych programistów, więc wszystko staje się singletonem i widzisz kod zaśmiecony referencjami Class :: get_instance (). Ogólnie mówiąc, istnieje tylko jeden lub dwa zasoby (na przykład połączenie z bazą danych), które kwalifikują się do użycia wzorca Singleton.
  2. Singletony są zasadniczo klasami statycznymi, polegającymi na jednej lub więcej statycznych metodach i właściwościach. Wszystkie rzeczy statyczne stanowią rzeczywiste, namacalne problemy podczas próby Testowania Jednostkowego, ponieważ reprezentują ślepe zaułki w twoim kodzie, których nie można wyśmiewać ani zgubić. W rezultacie, podczas testowania klasy opartej na Singletonie (lub dowolnej innej statycznej metodzie lub klasie), testujesz nie tylko tę klasę, ale także metodę statyczną lub klasę.

W wyniku obu z nich powszechnym podejściem jest użycie utworzenia szerokiego obiektu kontenera do przechowywania pojedynczej instancji tych klas i tylko obiekt kontenera modyfikuje te typy klas, podczas gdy wielu innym klasom można przyznać dostęp do nich z poziomu obiekt kontenerowy.

Noah Goodrich
źródło
6
Chciałbym tylko powiedzieć, że możesz wyśmiewać metody statyczne w Javie za pomocą JMockit ( code.google.com/p/jmockit ). To bardzo przydatne narzędzie.
Michael K
3
Kilka przemyśleń: pojemnik to Singleton, więc nie pozbyłeś się Singletonów. Ponadto, jeśli piszesz bibliotekę z publicznym interfejsem API, nie możesz zmusić użytkownika interfejsu API do korzystania z kontenera. Jeśli potrzebujesz globalnego menedżera zasobów w swojej bibliotece (na przykład chroniąc dostęp do pojedynczego zasobu fizycznego), musisz użyć Singleton.
Scott Whitlock,
2
@ Scott Whitlock, dlaczego to musi być singleton? Tylko dlatego, że istnieje tylko jedna instancja, tak nie jest. Każda biblioteka IoC
poradzi
To rozwiązanie zaproponowało, że jest również znany jako Toolbox
cregox
41

Zgadzam się, że jest to anty-wzór. Dlaczego? Ponieważ pozwala kłamać kodowi na temat jego zależności i nie możesz ufać innym programistom, że nie wprowadzą stanu zmiennego w twoich wcześniej niezmiennych singletonach.

Klasa może mieć konstruktor, który pobiera tylko ciąg, więc uważasz, że jest on tworzony w oderwaniu i nie ma skutków ubocznych. Jednak po cichu komunikuje się z jakimś publicznym, globalnie dostępnym obiektem singleton, dzięki czemu za każdym razem, gdy tworzysz instancję klasy, zawiera ona różne dane. Jest to duży problem, nie tylko dla użytkowników twojego API, ale także dla testowalności kodu. Aby poprawnie przetestować kod jednostkowo, musisz mikro-zarządzać i być świadomym globalnego stanu w singletonie, aby uzyskać spójne wyniki testu.

Magnus Wolffelt
źródło
Chciałbym móc głosować więcej niż raz!
MattDavey,
34

Wzorzec Singleton jest po prostu leniwie zainicjalizowaną zmienną globalną. Zmienne globalne są ogólnie i słusznie uważane za złe, ponieważ umożliwiają upiorne działanie na odległość między pozornie niepowiązanymi częściami programu. Jednak IMHO nie ma nic złego w zmiennych globalnych, które są ustawiane raz, z jednego miejsca, jako część procedury inicjalizacji programu (na przykład poprzez odczyt pliku konfiguracyjnego lub argumentów wiersza poleceń) i traktowane jako stałe. Takie użycie zmiennych globalnych różni się tylko literą, a nie duchem, od zadeklarowania nazwanej stałej w czasie kompilacji.

Podobnie, moim zdaniem Singletonów jest to, że są złe wtedy i tylko wtedy, gdy są używane do przekazywania stanu zmienności między pozornie niepowiązanymi częściami programu. Jeśli nie zawierają one stanu zmiennego lub jeśli stan zmienny, który zawierają, jest całkowicie zamknięty, aby użytkownicy obiektu nie musieli o tym wiedzieć, nawet w środowisku wielowątkowym, to nie ma w tym nic złego.

dsimcha
źródło
3
Innymi słowy, jesteś przeciwny używaniu bazy danych w programie.
Kendall Helmstetter Gelner
Jest słynny film z Google Tech Talk, który odzwierciedla Twoją opinię youtube.com/watch?v=-FRm3VPhseI
Martin York,
2
@KendallHelmstetterGelner: Istnieje różnica między pamięcią stanową a dodatkową. Jest mało prawdopodobne, abyś mógł przechowywać całą bazę danych w pamięci.
Martin York,
7

Dlaczego ludzie go używają?

Widziałem całkiem sporo singletonów w świecie PHP. Nie pamiętam żadnego przypadku użycia, w którym uznałem wzór za uzasadniony. Ale myślę, że mam pomysł na motywację, dlaczego ludzie go używali.

  1. Pojedyncze wystąpienie .

    „Użyj jednej instancji klasy C w całej aplikacji”.

    Jest to uzasadniony wymóg, np. W przypadku „domyślnego połączenia z bazą danych”. Nie oznacza to, że nigdy nie utworzysz drugiego połączenia db, to po prostu oznacza, że ​​zwykle pracujesz z domyślnym.

  2. Pojedyncza instancja .

    „Nie należy zezwalać na tworzenie instancji klasy C więcej niż jeden raz (na proces, na żądanie itp.).”

    Jest to istotne tylko wtedy, gdy utworzenie instancji klasy miałoby skutki uboczne sprzeczne z innymi instancjami.

    Często konfliktów tych można uniknąć, przeprojektowując komponent - np. Eliminując skutki uboczne z konstruktora klasy. Lub można je rozwiązać na inne sposoby. Ale nadal mogą istnieć pewne uzasadnione przypadki użycia.

    Powinieneś również pomyśleć o tym, czy wymóg „tylko jeden” naprawdę oznacza „jeden na proces”. Np. W przypadku współbieżności zasobów wymaganie to raczej „jeden na cały system, między procesami”, a nie „jeden na proces”. W przypadku innych rzeczy jest to raczej „kontekst aplikacji”, a ty po prostu masz jeden kontekst aplikacji na proces.

    W przeciwnym razie nie ma potrzeby egzekwowania tego założenia. Wymuszenie tego oznacza również, że nie można utworzyć osobnej instancji dla testów jednostkowych.

  3. Globalny dostęp.

    Jest to uzasadnione tylko wtedy, gdy nie masz odpowiedniej infrastruktury do przekazywania obiektów do miejsca, w którym są używane. Może to oznaczać, że twoja struktura lub środowisko jest do bani, ale naprawienie tego może nie być w twoich kompetencjach.

    Cena to ścisłe powiązanie, ukryte zależności i wszystko, co złe w stanie globalnym. Ale prawdopodobnie już cierpisz z tego powodu.

  4. Leniwa instancja.

    Nie jest to niezbędna część singletonu, ale wydaje się, że jest najpopularniejszym sposobem na ich wdrożenie. Ale chociaż leniwa instancja jest miłą rzeczą, tak naprawdę nie potrzebujesz singletona, aby to osiągnąć.

Typowa implementacja

Typowa implementacja to klasa z prywatnym konstruktorem i statyczną zmienną instancji oraz statyczna metoda getInstance () z opóźnioną instancją.

Oprócz wyżej wymienionych problemów, kłóci się to z zasadą pojedynczej odpowiedzialności , ponieważ klasa kontroluje własną instancję i cykl życia , oprócz innych obowiązków, które klasa już ma.

Wniosek

W wielu przypadkach można osiągnąć ten sam wynik bez singletonu i bez stanu globalnego. Zamiast tego należy użyć wstrzykiwania zależności, a może warto rozważyć kontener wstrzykiwania zależności .

Istnieją jednak przypadki użycia, w których pozostały następujące ważne wymagania:

  • Pojedyncza instancja (ale nie pojedyncza instancja)
  • Globalny dostęp (ponieważ pracujesz w ramach epoki brązu)
  • Leniwa instancja (bo to po prostu miło mieć)

Oto, co możesz zrobić w tym przypadku:

  • Utwórz klasę C, którą chcesz utworzyć za pomocą publicznego konstruktora.

  • Utwórz osobną klasę S ze zmienną instancji statycznej i statyczną metodą S :: getInstance () z leniwą instancją, która użyje klasy C dla instancji.

  • Wyeliminuj wszystkie skutki uboczne z konstruktora C. Zamiast tego umieść te skutki uboczne w metodzie S :: getInstance ().

  • Jeśli masz więcej niż jedną klasę o powyższych wymaganiach, możesz rozważyć zarządzanie instancjami klasy za pomocą małego kontenera usług lokalnych i używanie instancji statycznej tylko dla kontenera. Tak więc S :: getContainer () da ci leniwie utworzony kontener usługi, a ty otrzymasz inne obiekty z kontenera.

  • Unikaj wywoływania statycznego getInstance () tam, gdzie możesz. Zamiast tego używaj wstrzykiwania zależności, o ile to możliwe. W szczególności, jeśli używasz podejścia kontenerowego z wieloma obiektami, które są od siebie zależne, żaden z nich nie powinien wywoływać S :: getContainer ().

  • Opcjonalnie utwórz interfejs implementowany przez klasę C i użyj go do udokumentowania wartości zwracanej przez S :: getInstance ().

(Czy nadal nazywamy to singletonem? Zostawiam to w sekcji komentarzy ..)

Korzyści:

  • Możesz utworzyć osobną instancję C do testowania jednostkowego, bez dotykania żadnego stanu globalnego.

  • Zarządzanie instancjami jest oddzielone od samej klasy -> oddzielenie obaw, zasada pojedynczej odpowiedzialności.

  • Byłoby dość łatwo pozwolić S :: getInstance () użyć innej klasy dla instancji, a nawet dynamicznie określić, której klasy użyć.

Don Kichot
źródło
1

Osobiście użyję singletonów, gdy będę potrzebować 1, 2 lub 3 lub pewnej ograniczonej liczby obiektów dla danej klasy. Albo chcę przekazać użytkownikowi mojej klasy, że nie chcę, aby wiele instancji mojej klasy zostało utworzonych, aby działała poprawnie.

Użyję go także tylko wtedy, gdy muszę go używać prawie wszędzie w kodzie i nie chcę przekazywać obiektu jako parametru do każdej klasy lub funkcji, która go potrzebuje.

Ponadto użyję singletonu tylko wtedy, gdy nie zakłóci to przejrzystości referencyjnej innej funkcji. To znaczy, że biorąc pod uwagę jakiś wkład, zawsze będzie generował ten sam wynik. Nie używam tego dla stanu globalnego. Chyba że ten stan globalny zostanie zainicjowany raz i nigdy nie zostanie zmieniony.

Jeśli nie chcesz go używać, zobacz powyższe 3 i zmień je na przeciwne.

Brian R. Bondy
źródło
3
-0 ale ja prawie -1. Wolę przekazać go wszędzie w kodzie zamiast używać singletonu, ALE jeśli jestem naprawdę leniwy, mogę to zrobić. Zamiast tego użyłbym globalnych zmiennych, jeśli tylko mogę lub członków statycznych. globalne zmienne są naprawdę dużo lepsze (dla mnie) niż singletony. Globals nie kłamią