Singleton: Jak go używać


291

Edycja: Z innego pytania podałem odpowiedź, która zawiera linki do wielu pytań / odpowiedzi na temat singletonów: Więcej informacji na temat singletonów tutaj:

Przeczytałem więc temat Singletons: dobry projekt czy kula?
A kłótnia wciąż trwa.

Widzę Singletony jako wzorzec projektowy (dobry i zły).

Problemem w Singleton nie jest wzorzec, ale użytkownicy (przepraszam wszystkich). Wszyscy i ich ojciec myślą, że mogą poprawnie wdrożyć jedną (a z wielu wywiadów, które przeprowadziłem, większość ludzi nie może). Ponieważ wszyscy myślą, że mogą wdrożyć prawidłowy Singleton, nadużywają Wzorca i używają go w sytuacjach, które nie są odpowiednie (zastępując zmienne globalne Singletonami!).

Głównymi pytaniami, na które należy odpowiedzieć, są:

  • Kiedy należy stosować Singleton
  • Jak poprawnie wdrożyć Singleton?

Mam nadzieję, że ten artykuł pozwoli nam zebrać razem w jednym miejscu (zamiast wyszukiwać w Google i przeszukiwać wiele witryn) wiarygodne źródło informacji o tym, kiedy (a potem jak) poprawnie używać Singletona. Odpowiednia byłaby również lista Anti-Usages i typowych złych implementacji wyjaśniająca, dlaczego nie działają i dla dobrych implementacji ich słabości.


Więc
ruszaj piłką: podniosę rękę i powiem, że tego właśnie używam, ale prawdopodobnie mam problemy.
Podoba mi się „Scott Myers” zajmujący się tym tematem w swoich książkach „Effective C ++”

Dobre sytuacje do korzystania z singletonów (nie wiele):

  • Ramy rejestrowania
  • Pula recyklingu nici
/*
 * C++ Singleton
 * Limitation: Single Threaded Design
 * See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
 *      For problems associated with locking in multi threaded applications
 *
 * Limitation:
 * If you use this Singleton (A) within a destructor of another Singleton (B)
 * This Singleton (A) must be fully constructed before the constructor of (B)
 * is called.
 */
class MySingleton
{
    private:
        // Private Constructor
        MySingleton();
        // Stop the compiler generating methods of copy the object
        MySingleton(MySingleton const& copy);            // Not Implemented
        MySingleton& operator=(MySingleton const& copy); // Not Implemented

    public:
        static MySingleton& getInstance()
        {
            // The only instance
            // Guaranteed to be lazy initialized
            // Guaranteed that it will be destroyed correctly
            static MySingleton instance;
            return instance;
        }
};

OK. Pozyskajmy krytykę i inne implementacje razem.
:-)


36
Co jeśli później zdecydujesz, że chcesz wielu rejestratorów? Lub wiele pul wątków? Jeśli chcesz tylko jednego programu rejestrującego, utwórz tylko jedną instancję i ustaw ją jako globalną. Singletony są dobre tylko wtedy, gdy absolutnie POTRZEBUJESZ tam być tylko jeden i POTRZEBUJE być globalnym, IMHO.

3
Kto powiedział, że środowisko może mieć tylko 1 instancję rejestratora. Jeden singielton reprezentujący Framework. Framwork może następnie dać ci konkretne rejestratory.
Martin York

Tak. Nie użyłbym singeltong jako puli wątków. Po prostu rzucam pomysły, by wywołać odpowiedzi.
Martin York

@ Dan Singleton, który wdraża wzorzec strategii. Zachowanie jest abstrahowane od singletonu. Singleton to pojedynczy punkt wejścia. Nie masz dwóch programów rejestrujących, jeden program rejestrujący może decydować o sposobie logowania. Nie można wyprowadzać danych tylko do jednego dziennika na raz, nie trzeba mieć dwóch.
Lee Louviere

5
Xaade: co jeśli chcesz zalogować się do dwóch plików? Lub do bazy danych? A może gniazdko sieciowe? Lub widżet GUI? Chodzi o to, nie dodawaj sztucznych ograniczeń - nie ma takiej potrzeby. Jak często przypadkowo tworzyłeś dwie pętle zamiast jednej? Jeśli chcesz tylko jednego programu rejestrującego, utwórz tylko jeden.

Odpowiedzi:


181

Wszyscy się mylicie. Przeczytaj pytanie Odpowiedź:

Użyj singletonu, jeśli:

  • Musisz mieć jeden i tylko jeden obiekt typu w systemie

Nie używaj Singletona, jeśli:

  • Chcesz oszczędzać pamięć
  • Chcesz spróbować czegoś nowego
  • Chcesz pochwalić się tym, ile wiesz
  • Ponieważ wszyscy to robią (patrz programista kultu ładunków na wikipedii)
  • W widżetach interfejsu użytkownika
  • To ma być pamięć podręczna
  • W struny
  • W sesjach
  • Mogę iść cały dzień

Jak stworzyć najlepszy singleton:

  • Im mniejszy, tym lepiej. Jestem minimalistką
  • Upewnij się, że jest bezpieczny dla wątków
  • Upewnij się, że nigdy nie jest zerowy
  • Upewnij się, że utworzono go tylko raz
  • Leniwa czy inicjalizacja systemu? Do twoich wymagań
  • Czasami system operacyjny lub JVM tworzy dla ciebie singletony (np. W Javie każda definicja klasy jest singletonem)
  • Podaj destruktor lub wymyśl jak pozbyć się zasobów
  • Używaj mało pamięci

14
Właściwie myślę, że też nie masz całkowitej racji. Sformułowałbym: „Jeśli potrzebujesz mieć jeden i tylko jeden obiekt typu w systemie ORAZ musisz mieć do niego globalny dostęp” Nacisk na potrzebę jest mój - nie rób tego, jeśli jest to wygodne, tylko jeśli MUSI to mieć.

91
Też się mylisz. Jeśli potrzebujesz jednego i tylko jednego obiektu, tworzysz jeden i tylko jeden. Jeśli nie ma logicznego sposobu, aby dwa wystąpienia mogły zostać kiedykolwiek przyjęte bez nieodwracalnego uszkodzenia aplikacji, należy rozważyć utworzenie z niej singletonu. I jest jeszcze jeden aspekt, globalny dostęp: jeśli nie potrzebujesz globalnego dostępu do instancji, nie powinien to być singleton.
czerwiec

4
Zamknięty dla modyfikacji, otwarty dla rozszerzenia. Problem polega na tym, że nie można przedłużyć singletona na duoton lub tripleton. Utknął jako singleton.
Lee Louviere,

2
@ enzom83: A Capital-S Singleton zawiera kod zapewniający jego jednolitość. Jeśli chcesz tylko jednej instancji, możesz utracić ten kod i po prostu sam ją utworzyć ... co daje oszczędność pamięci jednej instancji, a także oszczędności wynikające z rezygnacji z kodu wymuszającego wyłączność - co oznacza również nie poświęcanie możliwość utworzenia drugiej instancji, jeśli kiedykolwiek zmienią się twoje wymagania.
cHao

4
„Jeśli potrzebujesz mieć jeden i tylko jeden obiekt typu w systemie” - „… i nigdy nie chcesz wyśmiewać tego obiektu w teście jednostkowym”.
Cygon

72

Singletony dają możliwość połączenia dwóch złych cech w jednej klasie. To źle pod każdym względem.

Singleton zapewnia:

  1. Globalny dostęp do obiektu oraz
  2. Gwarancja, że nigdy nie można utworzyć więcej niż jednego obiektu tego typu

Numer jeden jest prosty. Globale są ogólnie złe. Nigdy nie powinniśmy udostępniać obiektów globalnie, chyba że my naprawdę tego potrzebujemy.

Numer dwa może wydawać się sensowny, ale zastanówmy się nad tym. Kiedy ostatni raz ** przypadkowo * stworzyłeś nowy obiekt zamiast odwoływania się do istniejącego? Ponieważ jest to oznaczone jako C ++, skorzystajmy z przykładu z tego języka. Czy często przypadkowo piszesz?

std::ostream os;
os << "hello world\n";

Kiedy zamierzałeś pisać

std::cout << "hello world\n";

Oczywiście nie. Nie potrzebujemy ochrony przed tym błędem, ponieważ tego rodzaju błąd po prostu się nie zdarza. Jeśli tak, prawidłową odpowiedzią jest pójść do domu i spać przez 12-20 godzin, mając nadzieję, że poczujesz się lepiej.

Jeśli potrzebny jest tylko jeden obiekt, po prostu utwórz jedną instancję. Jeśli jeden obiekt powinien być globalnie dostępny, uczyń go globalnym. Ale to nie znaczy, że tworzenie innych instancji powinno być niemożliwe.

Ograniczenie „możliwa jest tylko jedna instancja” tak naprawdę nie chroni nas przed prawdopodobnymi błędami. Ale to ma uczynić nasz kod bardzo ciężko byłaby i utrzymaniu. Ponieważ dość często dowiadujemy się później , że potrzebowaliśmy więcej niż jednego wystąpienia. Możemy zrobić więcej niż jedną bazę danych, możemy nie mieć więcej niż jeden obiekt konfiguracji, chcemy kilku rejestratorów. Nasze testy jednostkowe mogą chcieć być w stanie tworzyć i odtwarzać te obiekty w każdym teście, biorąc przykład.

Więc pojedyncza powinny być stosowane tylko wtedy, gdy potrzebujemy zarówno cech, co oferuje: Jeśli trzeba globalny dostęp (co jest rzadkością, ponieważ globalne są na ogół zniechęca) , a my potrzebujemy aby nikt nigdy tworzenia więcej niż jednej instancji klasa (co wydaje mi się problemem projektowym). Jedyny powód, dla którego to widzę, to to, że utworzenie dwóch instancji spowodowałoby uszkodzenie naszego stanu aplikacji - prawdopodobnie dlatego, że klasa zawiera pewną liczbę elementów statycznych lub podobny głupotę. W takim przypadku oczywistą odpowiedzią jest naprawienie tej klasy. Nie powinno to zależeć od bycia jedyną instancją.

Jeśli potrzebujesz globalnego dostępu do obiektu, zmień go na globalny std::cout. Ale nie ograniczaj liczby instancji, które można utworzyć.

Jeśli absolutnie, pozytywnie potrzebujesz ograniczyć liczbę instancji klasy do jednej, i nie ma możliwości, aby utworzenie drugiej instancji kiedykolwiek mogło być obsługiwane bezpiecznie, to wymusz to. Ale nie udostępniaj go również globalnie.

Jeśli potrzebujesz obu cech, to 1) uczyń z niego singletona, a 2) daj mi znać, do czego go potrzebujesz, ponieważ trudno mi sobie wyobrazić taki przypadek.


3
lub możesz uczynić go globalnym i uzyskać tylko jedną wadę singletona. Dzięki singletonowi możesz jednocześnie ograniczyć się do jednego wystąpienia tej klasy bazy danych. Dlaczego to robisz Możesz też sprawdzić, dlaczego masz tak wiele zależności, że lista instancji staje się „naprawdę długa”. Czy wszystkie są potrzebne? Czy niektóre z nich należy przekazać innym komponentom? Być może niektóre z nich mogą być spakowane razem w strukturze, abyśmy mogli przekazać je jako pojedynczy argument. Istnieje wiele rozwiązań, wszystkie lepsze niż singletony.
jalf

6
Tak, singleton może być uzasadniony. Ale myślę, że właśnie udowodniłeś, że jest to konieczne tylko w dość egzotycznych przypadkach. Większość oprogramowania nie obsługuje sprzętu do odśnieżania. Ale nadal nie jestem przekonany. Zgadzam się, że w swojej aplikacji chcesz tylko jeden z nich. A co z testami jednostkowymi? Każdy z nich powinien działać w izolacji, dlatego najlepiej byłoby stworzyć własny SpreaderController - co jest trudne do zrobienia w singletonie. Wreszcie, dlaczego Twoi współpracownicy stworzyliby wiele instancji w pierwszej kolejności? Czy to realistyczny scenariusz do ochrony?
jalf

3
I pominąłeś fakt, że chociaż twoje dwa ostatnie przykłady prawdopodobnie uzasadniają ograniczenie „tylko jednej instancji”, nie robią nic, aby uzasadnić to „globalnie dostępne”. Dlaczego, u licha, cała baza kodów ma mieć dostęp do jednostki administracyjnej twojego przełącznika telefonu? Chodzi o to, aby dać ci obie cechy. Jeśli potrzebujesz tylko jednego lub drugiego, nie powinieneś używać singletonu.
jalf

2
@ jalf - Moim celem było tylko podanie przykładu, w którym Singleton jest przydatny na wolności, ponieważ nie można sobie wyobrazić; Wydaje mi się, że nie widzisz wiele razy, aby zastosować to w swojej obecnej pracy. Włączyłem programowanie pługów śnieżnych z aplikacji biznesowych wyłącznie dlatego, że pozwoliłoby mi to korzystać z Singleton. :) j / k Zgadzam się z twoim założeniem, że są lepsze sposoby na robienie tych rzeczy, dałeś mi wiele do przemyślenia. Dzięki za dyskusję!
J. Polfer

2
Używanie „wzorca” singletonu ( AHEM! ), Aby uniemożliwić ludziom tworzenie instancji większej liczby wystąpień, jest po prostu głupie po prostu, aby uniemożliwić ludziom takie działanie. Kiedy w mojej małej funkcji mam lokalną zmienną foo1 typu Foo i chcę tylko jedną w funkcji, nie martwię się, że ktoś stworzy drugą zmienną Foo2 foo2 i użyje jej zamiast oryginalnej.
Thomas Eding

36

Problemem singletonów nie jest ich implementacja. Chodzi o to, że łączą dwa różne pojęcia, z których żadne nie jest oczywiście pożądane.

1) Singletony zapewniają globalny mechanizm dostępu do obiektu. Chociaż mogą być nieznacznie bardziej wątkowo bezpieczne lub marginalnie bardziej niezawodne w językach bez dobrze zdefiniowanej kolejności inicjalizacji, to użycie jest nadal moralnym odpowiednikiem zmiennej globalnej. Jest to zmienna globalna ubrana w niezręczną składnię (powiedzmy foo :: get_instance () zamiast g_foo), ale służy dokładnie temu samemu celowi (pojedynczy obiekt dostępny w całym programie) i ma te same wady.

2) Singletony zapobiegają wielu wystąpieniom klasy. IME rzadko zdarza się, że tego rodzaju funkcje należy upiec w klasie. Zwykle jest to coś bardziej kontekstowego; wiele rzeczy, które są uważane za jeden i tylko jeden, tak naprawdę jest tak naprawdę tylko jednym. IMO bardziej odpowiednim rozwiązaniem jest utworzenie tylko jednej instancji - dopóki nie uświadomisz sobie, że potrzebujesz więcej niż jednej instancji.


6
Zgoda. Według niektórych, dwa zła mogą uczynić dobro w prawdziwym świecie. Ale w programowaniu mieszanie dwóch złych pomysłów nie skutkuje dobrym.
lipiec

27

Jedna rzecz ze wzorami: nie generalizuj . Mają wszystkie przypadki, gdy są przydatne i kiedy zawodzą.

Singleton może być nieprzyjemny, gdy trzeba przetestować kod. Na ogół utkniesz w jednej instancji klasy i możesz wybierać między otwarciem drzwi w konstruktorze lub inną metodą resetowania stanu i tak dalej.

Innym problemem jest to, że Singleton w rzeczywistości jest jedynie ukrytą zmienną globalną . Kiedy masz za dużo globalnego wspólnego stanu w swoim programie, wszystko ma tendencję do cofania się, wszyscy o tym wiemy.

Może to utrudnić śledzenie zależności . Kiedy wszystko zależy od twojego Singletona, trudniej jest go zmienić, podzielić na dwa itd. Na ogół utkniesz z tym. Utrudnia to także elastyczność. Sprawdź niektóre ramy wstrzykiwania zależności , aby spróbować rozwiązać ten problem.


8
Nie, singleton to coś więcej niż ukryta zmienna globalna. To sprawia, że ​​jest to szczególnie złe. Łączy globalność (która zwykle jest zła) z inną koncepcją, która również jest zła (polegająca na tym, że programista nie pozwala na utworzenie klasy, jeśli zdecyduje, że potrzebuje instancji). Często są one używane jako zmienne globalne, tak. A potem wciągają również inny paskudny efekt uboczny i kaleką bazę kodów.
lipiec

7
Należy również zauważyć, że singletony nie muszą mieć publicznej dostępności. Singleton może bardzo dobrze być wewnętrzną biblioteką i nigdy nie być narażony na działanie użytkownika. W tym sensie niekoniecznie są one „globalne”.
Steven Evers

1
+1 za wskazanie, ile bólu Singletony testują.
DevSolar

1
@jalf Niedopuszczenie, aby ktoś utworzył więcej niż jedną instancję klasy, nie jest złą rzeczą. Jeśli naprawdę istnieje tylko jedna instancja instancji klasy, która wymusza wymóg. Jeśli ktoś zdecyduje później, że musi utworzyć inną instancję, powinien ją przefaktoryzować, ponieważ nigdy nie powinien to być singleton.
William

2
@William: od czasu do czasu musiałem mieć wielu rejestratorów. Nie walczysz o singletona, ale zwykłego, starego miejscowego. Chcecie wiedzieć, że Rejestrator jest zawsze dostępna. Po to jest globalny. Nie musisz wiedzieć, że żaden inny program rejestrujący nie może zostać utworzony, co wymusza singleton. (spróbuj napisać testy jednostkowe dla twojego loggera - jest to o wiele łatwiejsze, jeśli możesz go utworzyć i zniszczyć w razie potrzeby, i nie jest to możliwe w singletonie)
czerwiec

13

Singletony zasadniczo pozwalają na uzyskanie złożonego globalnego stanu w językach, które w innym przypadku utrudniają lub uniemożliwiają tworzenie złożonych zmiennych globalnych.

W szczególności Java używa singletonów jako zamiennika zmiennych globalnych, ponieważ wszystko musi być zawarte w klasie. Najbardziej zbliżone do zmiennych globalnych są publiczne zmienne statyczne, które można stosować tak, jakby były globalneimport static

C ++ ma zmienne globalne, ale kolejność wywoływania konstruktorów zmiennych klasy globalnej jest nieokreślona. Jako taki singleton pozwala odroczyć utworzenie zmiennej globalnej do momentu, gdy zmienna ta będzie potrzebna.

Języki takie jak Python i Ruby bardzo rzadko korzystają z singletonów, ponieważ zamiast tego można używać zmiennych globalnych w module.

Kiedy więc warto używać singletona? Prawie dokładnie wtedy, gdy dobrze / źle byłoby użyć zmiennej globalnej.


Kiedy zmienna globalna jest „dobra”? Czasami są najlepszym rozwiązaniem problemu, ale nigdy nie są „dobre”.
DevSolar

1
zmienna globalna jest dobra, gdy jest używana wszędzie i wszystko może mieć do niej dostęp. Implementacja pojedynczej maszyny Turinga może wykorzystywać singleton.
Lee Louviere

Podoba mi się warstwa pośrednia w tej odpowiedzi: „kiedy byłoby dobrze / źle używać globalnego”. Zarówno DevSolar, jak i Lee Louviere otrzymują wartość, z którą się zgadzają, chociaż w czasie odpowiedzi nie było wiadomo, kto by skomentował.
Praxeolitic

6

Nowoczesny projekt C ++ autorstwa Alexandrescu ma bezpieczny dla wątków, dziedziczny ogólny singleton.

Dla mojej wartości 2 pensów uważam, że ważne jest, aby zdefiniować okresy życia swoich singletonów (kiedy jest to absolutnie konieczne, aby z nich skorzystać). Zwykle nie pozwalam, aby get()funkcja statyczna tworzyła cokolwiek, a konfigurację i zniszczenie pozostawiam w dedykowanej sekcji głównej aplikacji. Pomaga to uwypuklić zależności między singletonami - ale, jak podkreślono powyżej, najlepiej jest ich unikać, jeśli to możliwe.


6
  • Jak poprawnie wdrożyć Singleton?

Jest jeden problem, o którym nigdy nie wspominałem, coś, na co wpadłem podczas poprzedniej pracy. Mieliśmy singletony C ++, które były współdzielone między bibliotekami DLL, i zwykła mechanika zapewniania, że ​​pojedyncze wystąpienie klasy po prostu nie działa. Problem polega na tym, że każda biblioteka DLL otrzymuje własny zestaw zmiennych statycznych wraz z plikiem EXE. Jeśli twoja funkcja get_instance jest wbudowana lub stanowi część biblioteki statycznej, każda biblioteka DLL skończy z własną kopią „singletonu”.

Rozwiązaniem jest upewnienie się, że kod singletonu jest zdefiniowany tylko w jednej bibliotece DLL lub EXE, lub utworzenie menedżera singletonów z tymi właściwościami w celu sparowania wystąpień.


10
Yo dawg, słyszałem, że lubisz Singletony, więc stworzyłem Singleton dla swojego Singletona, abyś mógł przeciwdziałać wzorowi, podczas gdy ty przeciwdziałasz wzorowi.
Eva

@Eva, tak, coś takiego. Nie stworzyłem problemu, po prostu musiałem jakoś go uruchomić.
Mark Ransom

5

Pierwszy przykład nie jest bezpieczny dla wątków - jeśli dwa wątki wywołują getInstance w tym samym czasie, to statyczny będzie PITA. Pomocna byłaby jakaś forma muteksu.


Tak, co zostało odnotowane w komentarzach powyżej: * Ograniczenie: Projekt jednowątkowy * Patrz: aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf * Problemy związane z blokowaniem w aplikacjach wielowątkowych
Martin York,

Klasyczny singleton z getInstance jako metodą statyczną i metodami instancji dla innych operacji nigdy nie może być bezpieczny dla wątków. (cóż, chyba że zrobisz to pojedynczym tonem na tonę przy użyciu lokalnego przechowywania wątków ...)
Tobi

nawet w c ++ 11 lub nowszym?
hg_git

5

Jak zauważyli inni, głównymi wadami singletonów są niemożność ich rozszerzenia i utrata mocy tworzenia instancji więcej niż jednego wystąpienia, np. Do celów testowych.

Niektóre przydatne aspekty singletonów:

  1. leniwa lub początkowa instancja
  2. przydatne dla obiektu, który wymaga konfiguracji i / lub stanu

Jednak nie musisz używać singletonu, aby uzyskać te korzyści. Możesz napisać normalny obiekt, który wykonuje pracę, a następnie umożliwić ludziom dostęp do niego za pośrednictwem fabryki (osobny obiekt). Fabryka może martwić się o utworzenie jej i ponowne użycie itp., Jeśli zajdzie taka potrzeba. Ponadto, jeśli programujesz interfejs, a nie konkretną klasę, fabryka może stosować strategie, tzn. Możesz włączać i wyłączać różne implementacje interfejsu.

Wreszcie fabryka nadaje się do technologii wstrzykiwania zależności, takich jak Spring itp.


3

Singletony są przydatne, gdy wiele kodu jest uruchamianych podczas inicjowania i sprzeciwu. Na przykład, gdy używasz iBatis do konfigurowania obiektu trwałego, musi on przeczytać wszystkie konfiguracje, przeanalizować mapy, upewnić się, że wszystko jest poprawne itp. Przed przejściem do twojego kodu.

Jeśli zrobiłbyś to za każdym razem, wydajność byłaby znacznie niższa. Używając go w singletonie, bierzesz to trafienie raz, a potem wszystkie kolejne połączenia nie muszą tego robić.


Prototype Wzór robi to za dobrze, a to jest bardziej elastyczny. Możesz go również użyć, gdy klient wykona wiele instancji drogiej klasy, ale tylko ograniczona liczba z nich ma inny stan. Na przykład tetronimos w Tetris.
Eva

3

Prawdziwym upadkiem Singletonów jest to, że niszczą dziedzictwo. Nie możesz uzyskać nowej klasy, która zapewni ci rozszerzoną funkcjonalność, chyba że masz dostęp do kodu, do którego odnosi się Singleton. Tak więc poza faktem, że Singleton sprawi, że kod będzie ciasno powiązany (naprawiony przez wzorzec strategii ... aka Dependency Injection), uniemożliwi również zamknięcie części kodu przed korektą (biblioteki współdzielone).

Dlatego nawet przykłady programów rejestrujących lub pul wątków są nieprawidłowe i powinny zostać zastąpione przez Strategie.


Sami rejestrujący nie powinni być singletonami. Ogólny system przesyłania wiadomości powinien być. Sami rejestratorzy są subskrybentami wiadomości rozgłoszeniowych.
CashCow

Pule nici również nie powinny być singletonami. Ogólne pytanie brzmi: czy kiedykolwiek chciałbyś więcej niż jednego z nich? Tak. Kiedy ich ostatnio używałem, mieliśmy 3 różne pule wątków w jednej aplikacji.
CashCow,

3

Większość ludzi korzysta z singletonów, kiedy chcą się dobrze czuć przy użyciu zmiennej globalnej. Istnieją uzasadnione zastosowania, ale w większości przypadków, gdy ludzie z nich korzystają, fakt, że może istnieć tylko jedna instancja, jest tylko trywialnym faktem w porównaniu z faktem, że jest ona dostępna na całym świecie.


3

Ponieważ singleton umożliwia utworzenie tylko jednej instancji, skutecznie kontroluje replikację instancji. na przykład nie potrzebujesz wielu instancji wyszukiwania - na przykład mapa odnośników Morse'a, więc pakowanie jej w klasę singleton jest trafne. I tylko dlatego, że masz jedną instancję klasy, nie oznacza to, że masz również ograniczoną liczbę odwołań do tej instancji. Możesz kolejkować połączenia (aby uniknąć problemów z wątkami) do instancji i konieczne zmiany efektów. Tak, ogólna forma singletonu jest ogólnoświatowa, z pewnością możesz zmodyfikować projekt, aby stworzyć singleton o bardziej ograniczonym dostępie. Nie zmęczyłem się tym wcześniej, ale na pewno wiem, że to możliwe. I wszystkim, którzy skomentowali stwierdzenie, że wzór singletonu jest całkowicie zły, powinniście wiedzieć:



2

Poniżej znajduje się lepsze podejście do implementacji bezpiecznego dla wątku wzoru singletonu z dezalokacją pamięci w samym destruktorze. Ale myślę, że destruktor powinien być opcjonalny, ponieważ instancja singletonu zostanie automatycznie zniszczona po zakończeniu programu:

#include<iostream>
#include<mutex>

using namespace std;
std::mutex mtx;

class MySingleton{
private:
    static MySingleton * singletonInstance;
    MySingleton();
    ~MySingleton();
public:
    static MySingleton* GetInstance();
    MySingleton(const MySingleton&) = delete;
    const MySingleton& operator=(const MySingleton&) = delete;
    MySingleton(MySingleton&& other) noexcept = delete;
    MySingleton& operator=(MySingleton&& other) noexcept = delete;
};

MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton(){ };
MySingleton::~MySingleton(){
    delete singletonInstance;
};

MySingleton* MySingleton::GetInstance(){
    if (singletonInstance == NULL){
        std::lock_guard<std::mutex> lock(mtx);
        if (singletonInstance == NULL)
            singletonInstance = new MySingleton();
    }
    return singletonInstance;
}

Jeśli chodzi o sytuacje, w których musimy użyć klas singletonowych, możemy: - Jeśli chcemy utrzymać stan instancji przez cały czas wykonywania programu Jeśli jesteśmy zaangażowani w zapisywanie w dzienniku wykonania aplikacji, w którym tylko jedna instancja pliku musi być używane .... i tak dalej. Będzie to zauważalne, jeśli ktoś może zasugerować optymalizację w moim powyższym kodzie.


2
To zdecydowanie nie jest lepsze. 1: Nie definiujesz semantyki własności za pomocą wskaźnika. Nigdy nie powinieneś używać wskaźników w C ++, chyba że przygotowałeś się do zarządzania nimi. 2: Używanie podwójnie sprawdzonej blokady jest przestarzałe i istnieją znacznie lepsze nowoczesne sposoby na zrobienie tego. 3: Twoje komentarze na temat zniszczenia są naiwne. Odzyskiwanie pamięci nie jest celem użycia destruktora, chodzi o oczyszczenie. Sugestie dotyczące lepszej wersji: Spójrz na pytanie. Przedstawiona tam wersja jest już znacznie lepsza.
Martin York,

1

Używam Singletonów jako testu wywiadu.

Kiedy proszę dewelopera o podanie kilku wzorców projektowych, jeśli jedyne, co potrafią wymienić, to Singleton, nie są zatrudniani.


45
Ostre i szybkie zasady dotyczące zatrudniania sprawią, że przegapisz szeroką gamę potencjalnych pracowników.
Karl

13
Istnieje szeroka różnorodność idiotów. To nie znaczy, że należy ich rozważyć przy zatrudnianiu. Jeśli ktoś w ogóle nie wspomina o żadnych wzorach projektowych, myślę, że byłby lepszy niż ktoś, kto zna singleton, a nie inne wzorce.
lipiec

3
Dla albumu - moja odpowiedź brzmiała z przymrużeniem oka. Podczas mojego rzeczywistego procesu wywiadu staram się ocenić, czy będziemy musieli uczyć kogoś w C ++ i jak będzie to trudne. Niektórzy z moich ulubionych kandydatów to ludzie, którzy NIE znają C ++ od samego początku, ale udało mi się z nimi o tym porozmawiać.
Matt Cruikshank

4
Głosuj w dół. Z mojego osobistego doświadczenia - programista może nie być w stanie nazwać żadnych innych wzorów poza Singletonem, ale to nie znaczy, że używa Singletonów. Osobiście używałem singletonów w swoim kodzie, ZANIM kiedykolwiek o nich słyszałem (nazwałem je „mądrzejszymi globalsami” - wiedziałem, czym jest globalny). Kiedy się o nich dowiedziałem, kiedy dowiedziałem się o ich zaletach i wadach - przestałem ich używać. Nagle testy jednostkowe stały się dla mnie znacznie bardziej interesujące, kiedy przestałem ... Czy to pogorszyło mnie programistą? Pfff ...
Paulius

3
Głosuję również za nonsensownym pytaniem „nazwij jakieś wzorce projektowe”. Projektowanie polega na zrozumieniu, jak stosować wzorce projektowe, a nie tylko na rozwijaniu ich nazwisk. Ok, to może nie uzasadniać negatywnej opinii, ale ta odpowiedź jest trolliczna.
CashCow

0

Anti-Usage:

Jednym z głównych problemów związanych z nadmiernym użyciem singletonów jest to, że wzorzec uniemożliwia łatwe rozszerzanie i zamianę alternatywnych implementacji. Nazwa klasy jest zakodowana na stałe wszędzie tam, gdzie używany jest singleton.


Zaliczone z dwóch powodów: 1. Singleton może wewnętrznie korzystać z polimorficznych instancji (na przykład globalny Logger stosuje polimorficzne strategie targetowania). 2. Nazwa singletonu może być typedef, więc kod faktycznie zależy od typedef.
topright gamedev

Skończyłem budować moją wersję singletonu, aby była rozszerzalna przy użyciu ciekawie powtarzającego się wzorca szablonu.
Zachary Kraus

0

Myślę, że jest to najbardziej niezawodna wersja dla C #:

using System;
using System.Collections;
using System.Threading;

namespace DoFactory.GangOfFour.Singleton.RealWorld
{

  // MainApp test application

  class MainApp
  {
    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Same instance?
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 server requests
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // "Singleton"

  class LoadBalancer
  {
    private static LoadBalancer instance;
    private ArrayList servers = new ArrayList();

    private Random random = new Random();

    // Lock synchronization object
    private static object syncLock = new object();

    // Constructor (protected)
    protected LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      // Support multithreaded applications through
      // 'Double checked locking' pattern which (once
      // the instance exists) avoids locking each
      // time the method is invoked
      if (instance == null)
      {
        lock (syncLock)
        {
          if (instance == null)
          {
            instance = new LoadBalancer();
          }
        }
      }

      return instance;
    }

    // Simple, but effective random load balancer

    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Oto wersja zoptymalizowana dla platformy .NET :

using System;
using System.Collections;

namespace DoFactory.GangOfFour.Singleton.NETOptimized
{

  // MainApp test application

  class MainApp
  {

    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Confirm these are the same instance
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 requests for a server
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // Singleton

  sealed class LoadBalancer
  {
    // Static members are lazily initialized.
    // .NET guarantees thread safety for static initialization
    private static readonly LoadBalancer instance =
      new LoadBalancer();

    private ArrayList servers = new ArrayList();
    private Random random = new Random();

    // Note: constructor is private.
    private LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      return instance;
    }

    // Simple, but effective load balancer
    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Możesz znaleźć ten wzór na dotfactory.com .


3
Możesz rozebrać części, które nie są specjalnie powiązane z Singletonami, aby ułatwić odczytanie kodu.
Martin York,

Ponadto twoja pierwsza wersja nie jest bezpieczna dla wątków z powodu możliwej zmiany kolejności odczytu / zapisu. Zobacz stackoverflow.com/questions/9666/…
Thomas Danecker,

5
Uh ... zły język? Pytanie jest oczywiście oznaczone jako C ++ .
DevSolar

0

Wzór singletonu Meyersa działa wystarczająco dobrze przez większość czasu, a czasami tak nie jest, aby szukać czegoś lepszego. Tak długo, jak konstruktor nigdy nie będzie rzucał i nie będzie zależności między singletonami.

Singleton jest implementacją globalnie dostępnego obiektu (GAO od teraz), chociaż nie wszystkie GAO są singletonami.

Rejestratory same w sobie nie powinny być singletonami, ale idealnie powinny być globalnie dostępne środki do rejestrowania, aby oddzielić miejsce, w którym komunikat dziennika jest generowany, skąd i jak się loguje.

Leniwe ładowanie / leniwa ewaluacja to inna koncepcja i singleton zwykle też to implementuje. Ma wiele własnych problemów, w szczególności bezpieczeństwo wątków i problemy, jeśli zawodzi z wyjątkami takimi, że to, co w tamtym czasie wydawało się dobrym pomysłem, okazuje się wcale nie takie świetne. (Trochę jak implementacja COW w ciągach).

Mając to na uwadze, GOA można zainicjować w następujący sposób:

namespace {

T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;

}

int main( int argc, char* argv[])
{
   T1 t1(args1);
   T2 t2(args2);
   T3 t3(args3);
   T4 t4(args4);

   pt1 = &t1;
   pt2 = &t2;
   pt3 = &t3;
   pt4 = &t4;

   dostuff();

}

T1& getT1()
{
   return *pt1;
}

T2& getT2()
{
   return *pt2;
}

T3& getT3()
{
  return *pt3;
}

T4& getT4()
{
  return *pt4;
}

Nie trzeba tego robić tak brutalnie, a wyraźnie w załadowanej bibliotece zawierającej obiekty prawdopodobnie potrzebujesz innego mechanizmu do zarządzania ich żywotnością. (Umieść je w obiekcie, który otrzymasz po załadowaniu biblioteki).

A kiedy używam singletonów? Użyłem ich do dwóch rzeczy - tabeli singletonów, która wskazuje, jakie biblioteki zostały załadowane dlopen - programu obsługi komunikatów, do którego logujący mogą się zapisać i do którego możesz wysyłać wiadomości. Wymagany specjalnie dla programów obsługi sygnałów.


0

Nadal nie rozumiem, dlaczego singleton musi być globalny.

Zamierzałem stworzyć singletona, w którym ukryłem bazę danych wewnątrz klasy jako prywatną stałą zmienną statyczną i stworzyłem funkcje klasy, które wykorzystują bazę danych bez ujawniania jej użytkownikowi.

Nie rozumiem, dlaczego ta funkcjonalność byłaby zła.


Nie rozumiem, dlaczego uważasz, że to musi być globalne.
Martin York

według tego wątku, wszyscy mówili, że singleton musi być globalny
Zachary Kraus

1
Nie. Wątek wskazuje, że singelton ma stan globalny. Nie dlatego, że jest to zmienna globalna. Proponowane przez Ciebie rozwiązanie ma stan globalny. Proponowane rozwiązanie wykorzystuje również zmienną globalną; element statyczny klasy jest obiektem „Static Storage Duration”, zmienna globalna jest obiektem „Static Storage Duration”. Tak więc oba są zasadniczo takie same z nieco innymi semantykami / zakresami.
Martin York

Czyli prywatna zmienna statyczna jest nadal globalna ze względu na „Statyczny czas przechowywania”?
Zachary Kraus

1
Uwaga: Tęskniłeś za moim umyślnie żadnym z wymienionych. Twój projekt użycia statycznego „prywatnego” członka nie jest taki sam jak singelton. Ponieważ nie wprowadza „globalnego stanu zmiennego”. Ale to też nie jest singleton. Singleton to klasa zaprojektowana tak, aby istniała tylko jedna instancja obiektu. Sugerujesz, aby pojedynczy stan współużytkowany był dla wszystkich obiektów klasy. Inna koncepcja.
Martin York

0

Uważam je za przydatne, gdy mam zajęcia, które zawierają wiele pamięci. Na przykład w ostatniej grze, nad którą pracowałem, mam klasę mapy wpływów, która zawiera kolekcję bardzo dużych tablic ciągłej pamięci. Chcę, aby wszystkie zostały przydzielone podczas uruchamiania, wszystkie uwolnione przy wyłączaniu i zdecydowanie chcę tylko jedną kopię. Muszę też uzyskać do niego dostęp z wielu miejsc. Uważam, że wzór singletonu jest bardzo przydatny w tym przypadku.

Jestem pewien, że istnieją inne rozwiązania, ale uważam to za bardzo przydatne i łatwe do wdrożenia.


0

Jeśli jesteś tym, który stworzył singleton i używa go, nie rób z niego singletonu (nie ma to sensu, ponieważ możesz kontrolować osobliwość obiektu bez uczynienia go singletonem), ale ma to sens, gdy jesteś deweloperem biblioteki i chcesz dostarczyć użytkownikom tylko jeden obiekt (w tym przypadku to Ty stworzyłeś singleton, ale nie jesteś użytkownikiem).

Singletony są obiektami, więc używaj ich jako obiektów, wiele osób uzyskuje dostęp do singletonów bezpośrednio przez wywołanie metody, która je zwraca, ale jest to szkodliwe, ponieważ sprawiasz, że twój kod wie, że obiekt jest singletonem, wolę używać singletonów jako obiektów, przekazuję je za pomocą konstruktora, a ja używam ich jako zwykłych obiektów, w ten sposób twój kod nie wie, czy te obiekty są singletonami, czy nie, a to czyni zależności bardziej przejrzystymi i pomaga trochę w refaktoryzacji ...


-1

W aplikacjach komputerowych (wiem, że tylko my dinozaury już je piszą!) Są one niezbędne do uzyskania względnie niezmiennych globalnych ustawień aplikacji - języka użytkownika, ścieżki do plików pomocy, preferencji użytkownika itp., Które w przeciwnym razie musiałyby się propagować w każdej klasie i każdym oknie dialogowym .

Edytuj - oczywiście powinny być tylko do odczytu!


Ale to nasuwa pytanie; dlaczego język użytkownika i ścieżka do pliku pomocy muszą w ogóle być metodą instancji ?
DrPizza

2
Mamy do tego globale. Nie ma potrzeby robienia z nich
singli

Zmienne globalne - w jaki sposób szeregujesz je z rejestru / bazy danych? Klasa Gobal - jak więc upewnić się, że jest tylko jedna z nich?
Martin Beckett

@mgb: serializujesz je, odczytując wartości z rejestru / bazy danych i przechowując je w zmiennych globalnych (prawdopodobnie należy to zrobić u góry głównej funkcji). upewniasz się, że istnieje tylko jeden obiekt klasy, tworząc tylko jeden obiekt klasy ... naprawdę ... czy trudno jest „grep -rn” nowy \ + nazwa_klasy_globalnej ”. ? naprawdę?
Paulius

7
@mgb: Dlaczego, u licha, miałbym zapewnić, że jest tylko jeden? Muszę tylko wiedzieć, że jedno wystąpienie zawsze reprezentuje bieżące ustawienia. ale nie ma powodu, dla którego nie powinienem mieć innych obiektów ustawień w tym miejscu. Być może na przykład dla „ustawień, które użytkownik obecnie definiuje, ale jeszcze nie zastosował”. Lub jeden dla „konfiguracji, którą użytkownik wcześniej zapisał, aby mógł do nich wrócić później”. Lub jeden dla każdego testu jednostkowego.
jalf

-1

Kolejne wdrożenie

class Singleton
{
public:
    static Singleton& Instance()
    {
        // lazy initialize
        if (instance_ == NULL) instance_ = new Singleton();

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};

3
To naprawdę okropne. Zobacz: stackoverflow.com/questions/1008019/c-singleton-design-pattern/..., aby uzyskać lepszą leniwą inicjalizację i, co ważniejsze, gwarantowane deterministyczne zniszczenie.
Martin York,

Jeśli zamierzasz używać wskaźników, Instance()powinieneś zwrócić wskaźnik, a nie referencję. Wewnątrz .cpppliku, zainicjować instancję na NULL: Singleton* Singleton::instance_ = nullptr;. I Instance()powinny być realizowane jako: if (instance_ == nullptr) instance_ = new Singleton(); return instance_;.
Dennis
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.