Czytałem różne opinie na temat wzoru singletonu. Niektórzy twierdzą, że należy tego unikać za wszelką cenę, a inni, że może być przydatny w niektórych sytuacjach.
Jedną z sytuacji, w której używam singletonów, jest sytuacja, w której potrzebuję fabryki (powiedzmy obiektu f typu F), aby utworzyć obiekty określonej klasy A. Fabryka jest tworzona raz przy użyciu niektórych parametrów konfiguracyjnych, a następnie jest używana za każdym razem tworzona jest instancja typu A. Tak więc każda część kodu, która chce utworzyć instancję A, pobiera singleton f i tworzy nową instancję, np
F& f = F::instance();
boost::shared_ptr<A> a = f.createA();
Więc mój ogólny scenariusz jest taki
- Potrzebuję tylko jednego wystąpienia klasy ze względów optymalizacyjnych (nie potrzebuję wielu obiektów fabryki) lub do wspólnego stanu (np. Fabryka wie, ile wystąpień klasy A może nadal utworzyć)
- Potrzebuję sposobu na dostęp do tego wystąpienia f z F w różnych miejscach kodu.
Nie interesuje mnie dyskusja, czy ten wzór jest dobry czy zły, ale zakładając, że chcę uniknąć używania singletonu, jakiego innego wzoru mogę użyć?
Pomysły, jakie miałem (1), aby uzyskać obiekt fabryki z rejestru lub (2), aby utworzyć fabrykę w pewnym momencie podczas uruchamiania programu, a następnie przekazać fabrykę jako parametr.
W rozwiązaniu (1) sam rejestr jest singletonem, więc właśnie przesunąłem problem nieużywania singletonu z fabryki do rejestru.
W przypadku (2) potrzebuję początkowego źródła (obiektu), z którego pochodzi obiekt fabryczny, dlatego obawiam się, że ponownie wrócę do innego singletonu (obiektu, który zapewnia moją instancję fabryki). Podążając za tym łańcuchem singletonów, mogę być może zredukować problem do jednego singletonu (całej aplikacji), za pomocą którego wszystkie inne singletony są zarządzane bezpośrednio lub pośrednio.
Czy ta ostatnia opcja (użycie jednego początkowego singletonu, który tworzy wszystkie inne unikalne obiekty i wstrzykuje wszystkie inne singletony we właściwe miejsca) byłaby akceptowalnym rozwiązaniem? Czy jest to rozwiązanie sugerowane domyślnie, gdy odradza się stosowanie singletonów, czy jakie są inne rozwiązania, np. W powyższym przykładzie?
EDYTOWAĆ
Ponieważ uważam, że punkt mojego pytania został przez niektórych źle zrozumiany, oto kilka dodatkowych informacji. Jak wyjaśniono np. Tutaj , słowo singleton może oznaczać (a) klasę z obiektem o pojedynczej instancji oraz (b) wzorzec projektowy użyty do utworzenia i uzyskania dostępu do takiego obiektu.
Aby to wyjaśnić, użyjmy terminu unikalny obiekt dla (a) i wzorca singletonu dla (b). Wiem więc, jaki jest wzorzec singletonu i wstrzykiwanie zależności (BTW, ostatnio intensywnie używam DI do usuwania instancji wzorca singletonu z kodu, nad którym pracuję).
Chodzi mi o to, że o ile nie zostanie utworzona instancja wykresu całego obiektu z jednego obiektu żyjącego na stosie głównej metody, zawsze będzie potrzeba dostępu do niektórych unikalnych obiektów poprzez wzorzec singletonu.
Moje pytanie brzmi, czy posiadanie pełnego tworzenia wykresów obiektowych i okablowania zależy od głównej metody (np. Za pomocą jakiegoś potężnego frameworka DI, który nie używa samego wzorca) jest jedynym rozwiązaniem wolnym od wzorca .
Odpowiedzi:
Druga opcja jest dobrym rozwiązaniem - jest to rodzaj zastrzyku zależności, który jest wzorcem używanym do dzielenia stanu w całym programie, gdy chcesz uniknąć singletonów i zmiennych globalnych.
Nie można obejść faktu, że coś musi stworzyć twoją fabrykę. Jeśli coś się dzieje z aplikacją, niech tak będzie. Ważne jest to, że twoja fabryka nie powinna dbać o to, jaki obiekt ją stworzył, a obiekty, które ją otrzymały, nie powinny zależeć od tego, skąd pochodzi fabryka. Nie pozwól, aby Twoje obiekty otrzymały wskaźnik do singletonu aplikacji i poprosiły o fabrykę; niech twoja aplikacja utworzy fabrykę i przekaże ją tym, które będą jej potrzebować.
źródło
Celem singletonu jest wymuszenie, że tylko jedna instancja może kiedykolwiek istnieć w określonym królestwie. Oznacza to, że singleton jest przydatny, jeśli masz poważne powody, aby wymusić zachowanie singletonu; w praktyce jednak rzadko tak się dzieje, a wieloprzetwarzanie i wielowątkowość z pewnością zacierają znaczenie słowa „unikatowy” - czy jest to jedna instancja na maszynę, na proces, na wątek, na żądanie? I czy Twoja implementacja singletona dba o warunki wyścigu?
Zamiast singletonu wolę używać jednego z:
Powodem jest to, że singleton jest ukrytym stanem globalnym, co oznacza, że wprowadza wysoki stopień sprzężenia w całej aplikacji - każda część kodu może pobrać instancję singletona z dowolnego miejsca przy minimalnym wysiłku. Jeśli używasz lokalnych obiektów lub przekazujesz instancje, masz o wiele większą kontrolę i możesz ograniczyć zakresy i zawęzić zależności.
źródło
Większość ludzi (łącznie z tobą) zupełnie nie rozumie, czym właściwie jest Singleton. Wzorzec Singleton oznacza tylko, że istnieje jedno wystąpienie klasy i istnieje mechanizm dla całej aplikacji, aby uzyskać odwołanie do tego wystąpienia.
W książce GoF metoda fabryki statycznej, która zwraca odniesienie do pola statycznego, była tylko przykładem tego, jak mógłby wyglądać ten mechanizm, i miała poważne wady. Niestety wszyscy i ich pies złapali się tego mechanizmu i myśleli, że o to właśnie chodzi w Singleton.
Przytaczane przez ciebie alternatywy to także Singletony, tylko z innym mechanizmem uzyskiwania referencji. (2) wyraźnie powoduje, że zbyt wiele przechodzi, chyba że potrzebujesz referencji tylko w kilku miejscach w pobliżu źródła stosu wywołań. (1) brzmi jak prymitywna platforma wstrzykiwania zależności - dlaczego więc jej nie użyć?
Zaletą jest to, że istniejące platformy DI są elastyczne, wydajne i dobrze przetestowane. Mogą zrobić znacznie więcej niż tylko zarządzać Singletonami. Jednak, aby w pełni wykorzystać ich możliwości, działają najlepiej, jeśli ustrukturyzujesz swoją aplikację w określony sposób, co nie zawsze jest możliwe: idealnie, są centralne obiekty, które są pozyskiwane za pomocą frameworka DI i mają wszystkie zależności zależnie od siebie i są wypełnione następnie wykonany.
Edycja: Ostatecznie wszystko i tak zależy od głównej metody. Ale masz rację: jedynym sposobem na całkowite uniknięcie użycia globalnego / statycznego jest skonfigurowanie wszystkiego od głównej metody. Zauważ, że DI jest najbardziej popularny w środowiskach serwerowych, gdzie główna metoda jest nieprzejrzysta i konfiguruje serwer, a kod aplikacji składa się zasadniczo z wywołań zwrotnych, które są tworzone i wywoływane przez kod serwera. Jednak większość platform DI umożliwia również bezpośredni dostęp do ich centralnego rejestru, np. Spring Application Application, w „szczególnych przypadkach”.
Więc w zasadzie najlepszą rzeczą, jaką ludzie wymyślili do tej pory, jest sprytna kombinacja dwóch alternatyw, o których wspomniałeś.
źródło
Istnieje kilka alternatyw:
Wstrzykiwanie zależności
Każdy obiekt ma swoje zależności przekazane mu podczas tworzenia. Zazwyczaj za tworzenie i okablowanie obiektów odpowiedzialna jest struktura lub niewielka liczba klas fabrycznych
Rejestr usług
Każdy obiekt jest przekazywany do obiektu rejestru usługi. Zapewnia metody żądania różnych różnych obiektów, które świadczą różne usługi. Struktura systemu Android używa tego wzorca
Menedżer root
Istnieje jeden obiekt, który jest rdzeniem grafu obiektowego. Podążając za linkami z tego obiektu można w końcu znaleźć dowolny inny obiekt. Kod zwykle wygląda następująco:
źródło
Singleton::instance
miejscu w kodzie klienta.Jeśli Twoje potrzeby związane z singletonem można sprowadzić do pojedynczej funkcji, dlaczego nie skorzystać z prostej funkcji fabrycznej? Funkcje globalne (prawdopodobnie w twoim przykładzie jest to metoda statyczna klasy F) są z natury singletonami, z wyjątkowością wymuszoną przez kompilator i linker.
Wprawdzie to się psuje, gdy działania singletonu nie można sprowadzić do pojedynczego wywołania funkcji, choć być może niewielki zestaw powiązanych funkcji może cię ominąć.
Biorąc to wszystko pod uwagę, punkty 1 i 2 w twoim pytaniu wyjaśniają, że naprawdę chcesz tylko czegoś. W zależności od definicji wzorca singletonu już go używasz lub jest on bardzo bliski. Nie sądzę, że możesz mieć coś takiego bez singletona lub przynajmniej bardzo blisko jednego. Jest zbyt blisko znaczenia singletonu.
Jak sugerujesz, w pewnym momencie musisz mieć coś, więc być może problemem nie jest jedno wystąpienie czegoś, ale podjęcie kroków w celu zapobieżenia (lub przynajmniej zniechęcenia lub zminimalizowania) nadużycia. Dobrym początkiem jest przeniesienie jak największej liczby stanów z „singletonu” do parametrów, jak to możliwe. Pomaga także zawężenie interfejsu do singletonu, ponieważ w ten sposób jest mniej okazji do nadużyć. Czasami musisz go po prostu wyssać i uczynić singleton bardzo solidnym, takim jak sterty lub system plików.
źródło
Jeśli używasz języka wieloparadigmowego, takiego jak C ++ lub Python, jedną alternatywą dla klasy singleton jest zestaw funkcji / zmiennych opakowanych w przestrzeń nazw.
Mówiąc koncepcyjnie, plik C ++ z wolnymi zmiennymi globalnymi, wolnymi funkcjami globalnymi i zmiennymi statycznymi używanymi do ukrywania informacji, wszystkie opakowane w przestrzeń nazw, daje prawie taki sam efekt jak singletonowa „klasa”.
Rozpada się tylko, jeśli chcesz dziedziczenia. Widziałem wiele singletonów, które byłyby lepsze w ten sposób.
źródło
Kapsułkowanie singletonów
Scenariusz przypadku Twoja aplikacja jest zorientowana obiektowo i wymaga 3 lub 4 określonych singletonów, nawet jeśli masz więcej klas.
Przed przykładem (C ++ jak pseudokod):
Jednym ze sposobów jest częściowe usunięcie niektórych (globalnych) singletonów poprzez kapsułkowanie ich wszystkich w unikalny singleton, który ma jako członków inne singletony.
W ten sposób, zamiast „kilku singletonów, podejście, w ogóle, brak singletonu, podejście”, mamy „enkapsulację wszystkich singletonów w jedno, podejście”.
Po przykładzie (C ++ jak pseudokod):
Pamiętaj, że przykłady bardziej przypominają pseudokod i ignorują drobne błędy lub błędy składniowe i rozważają rozwiązanie pytania.
Należy również rozważyć inne kwestie, ponieważ używany język programowania może wpływać na sposób implementacji implementacji singleton lub non singleton.
Twoje zdrowie.
źródło
Twoje oryginalne wymagania:
Nie zgadzaj się z definicją singletonu (i tym, do czego powinieneś się później odnieść). Z GoF (moja wersja to 1995) strona 127:
Jeśli potrzebujesz tylko jednego wystąpienia, nie wyklucza to zarabiania więcej.
Jeśli chcesz mieć jedną globalnie dostępną instancję, zrób jedną globalnie dostępną instancję . Nie trzeba mieć wzorca nazwanego dla wszystkiego, co robisz. I tak, pojedyncze globalnie dostępne wystąpienia są zwykle złe. Nadanie im nazwy nie czyni ich mniej złymi .
Aby uniknąć globalnej dostępności, pospolite podejście „zrób instancję i przekaż ją” lub „poproś właściciela dwóch obiektów o sklejenie ich razem” działa dobrze.
źródło
Co powiesz na korzystanie z kontenera IoC? Z pewnymi myślami możesz skończyć z czymś w rodzaju:
Musisz określić, która implementacja ma
IFoo
być używana podczas uruchamiania, ale jest to konfiguracja jednorazowa, którą możesz łatwo zastąpić później. Żywotność rozwiązanej instancji może normalnie być kontrolowana przez kontener IoC.Statyczną metodę singleton void można zastąpić:
Metoda statycznego getletu singletonowego można zastąpić:
Przykład dotyczy .NET, ale jestem pewien, że bardzo podobne można osiągnąć w innych językach.
źródło