Czy są jakieś realne alternatywy dla GOF Singleton Pattern?

91

Spojrzmy prawdzie w oczy. Wzór Singleton jest bardzo kontrowersyjnym tematem z hordami programistów po obu stronach ogrodzenia. Są tacy, którzy czują, że Singleton jest niczym innym jak gloryfikowaną zmienną globalną, i inni, którzy przysięgają na wzór i używają go nieustannie. Nie chcę jednak, aby Kontrowersje Singletona leżały u podstaw mojego pytania. Każdy może wziąć udział w przeciąganiu liny, walczyć i zobaczyć, kto wygra, o ile mi zależy . Próbuję powiedzieć, że nie wierzę, że istnieje jedna poprawna odpowiedź i nie próbuję celowo rozpalać kłótni partyzanckich. Interesują mnie po prostu alternatywy singletonowe, kiedy zadaję pytanie:

Czy są jakieś konkretne alternatywy dla wzoru GOF Singleton?

Na przykład wiele razy, gdy w przeszłości korzystałem ze wzorca singleton, po prostu interesuje mnie zachowanie stanu / wartości jednej lub kilku zmiennych. Stan / wartości zmiennych można jednak zachować między każdą instancją klasy przy użyciu zmiennych statycznych zamiast używania wzorca singleton.

Jaki masz inny pomysł?

EDYCJA: Naprawdę nie chcę, aby był to kolejny post o tym, jak poprawnie używać singletona. Ponownie szukam sposobów, aby tego uniknąć. Dla zabawy, ok? Chyba zadaję czysto akademickie pytanie w Twoim najlepszym głosie zwiastuna filmu: „W równoległym wszechświecie, w którym nie ma singletona, co możemy zrobić?”

Kodowanie bez komentarzy
źródło
Co? Ani dobrze, ani źle, ale jak mogę to wymienić? Dla wszystkich, którzy mówią, że to dobre - nie uczestnicz. Wszyscy ludzie, którzy mówią, że to złe, udowodnijcie to, pokazując mi, jak mogę bez tego żyć. Brzmi argumentująco.
S.Lott
@CodingWithoutComents: przeczytał cały post. W ten sposób mam poczucie „nie odpowiadaj, jeśli uważasz, że Singletony są w porządku”.
S.Lott
2
Cóż, jeśli tak się stało, przepraszam. Myślałem, że podjąłem znaczące kroki, aby uniknąć polaryzacji. Wydawało mi się, że zadałem to pytanie w taki sposób, że zarówno kochankowie, jak i nienawidzący Singletonów mogliby sobie z tym
poradzić
Jeśli używam Singletonów, nie mam żadnego wkładu w to, jak je obejść. Wydaje mi się to polaryzujące.
S.Lott
9
Używam Singletonów codziennie, ale czy to powstrzymuje mnie od myślenia, że ​​może być lepszy sposób na robienie rzeczy? Wzorce projektowe istnieją dopiero od 14 lat. Czy uważam je za prawdę biblijną? Czy przestajemy myśleć nieszablonowo? Czy nie staramy się rozwijać dyscypliny CS?
CodingBithoutComments

Odpowiedzi:

77

Alex Miller w „ Patterns I Hate ” cytuje:

„Kiedy singleton wydaje się być odpowiedzią, często uważam, że rozsądniej jest:

  1. Utwórz interfejs i domyślną implementację swojego singletona
  2. Skonstruuj pojedyncze wystąpienie domyślnej implementacji na „górze” systemu. Może to być w konfiguracji Springa, w kodzie lub zdefiniowane na różne sposoby w zależności od systemu.
  3. Przekaż pojedyncze wystąpienie do każdego składnika, który go potrzebuje (iniekcja zależności)
Jacek Szymański
źródło
2
Wiem, że ma prawie 5 lat, ale czy możesz to rozwinąć? Myślę, że nauczono mnie, że właściwym sposobem na stworzenie singletona jest interfejs. Byłbym zainteresowany, czy to, co robię, było „OK”, a nie tak straszne, jak mi powiedziano.
Pureferret
97

Aby zrozumieć właściwy sposób obejścia singletonów, musisz zrozumieć, co jest nie tak z singletonami (i ogólnie ze stanem globalnym):

Singletony ukrywają zależności.

Dlaczego jest to ważne?

Ponieważ jeśli ukryjesz zależności, zwykle tracisz kontrolę nad wielkością sprzężenia.

Możesz się z tym spierać

void purchaseLaptop(String creditCardNumber, int price){
  CreditCardProcessor.getInstance().debit(creditCardNumber, amount);
  Cart.getInstance().addLaptop();
}

jest prostsze niż

void purchaseLaptop(CreditCardProcessor creditCardProcessor, Cart cart, 
                    String creditCardNumber, int price){
  creditCardProcessor.debit(creditCardNumber, amount);
  cart.addLaptop();
}

ale przynajmniej drugie API wyjaśnia dokładnie, kim są współpracownicy metody.

Tak więc sposobem obejścia Singletonów nie jest użycie statycznych zmiennych lub lokalizatorów usług, ale zmiana klas Singleton w instancje, które są tworzone w zakresie, w którym mają sens i są wstrzykiwane do komponentów i metod, które ich potrzebują. Możesz użyć frameworka IoC, aby sobie z tym poradzić, lub możesz to zrobić ręcznie, ale ważne jest, aby pozbyć się swojego stanu globalnego i wyraźnie określić zależności i współpracę.

Rasmus Faber
źródło
+1 do omówienia źródła problemu, a nie tylko sposobu jego obejścia.
Phil
9
W pełnoprawnej wielowarstwowej aplikacji druga metoda często tworzy ogromną ilość niepotrzebnego kodu. Zanieczyszczając kod środkowych warstw logiką przenoszoną na niższe poziomy, obiekty, które w innym przypadku są niepotrzebne na tym poziomie, czy nie tworzymy zależności, które nie muszą być? Powiedz, że warstwa 4 w stosie nawigacji inicjuje działanie w tle, takie jak przesłanie pliku. Teraz powiedzmy, że chcesz ostrzec użytkownika, gdy zakończy się, ale do tego czasu użytkownik może znajdować się w zupełnie innej części aplikacji, która ma tylko warstwę 1 wspólną z widokiem inicjującym. Mnóstwo niepotrzebnego kodu ...
Hari Karam Singh
1
@RasmusFaber Wzorzec Singleton nie ukrywa zależności. Narzekasz, że ukrywanie zależności jest kwestią odrębną od wzorca. Prawdą jest, że wzór ułatwia popełnienie tego błędu, ale bardzo możliwe jest harmonijne wykonanie wzorca IoC i Singleton. Na przykład mogę utworzyć bibliotekę innej firmy, która wymaga specjalnego zarządzania cyklem życia jej instancji, a każdy, kto korzysta z mojej biblioteki, powinien nadal „przekazywać” instancję mojego singletona do swoich klas przy użyciu klasycznego narzędzia Dependency Injection, a nie „sky hook”, mojego singletona z każdego punktu.
Martin Bliss
@HariKaramSingh możesz użyć wzorca subskrypcji / publikacji lub szyny zdarzeń dla całego systemu (która musi być współdzielona przez wszystkie zainteresowane strony, a nie pojedynczą), aby obejść ten problem.
Victor Sorokin
4
Również wzorce pub / sub lub observer mogą być tak samo złe w przypadku „utraty połączenia”, o czym potwierdzi każdy, kto musiał przejść debugowanie przez framework, który w dużym stopniu na nich opiera się. Przynajmniej w przypadku singletonów wywoływana nazwa metody znajduje się w tym samym miejscu, co wywołujący kod :) Osobiście uważam, że wszystkie te strategie mają swoje miejsce i żadnej nie można zaimplementować na ślepo. Niechlujni programiści zrobią spaghetti z dowolnego wzoru, a odpowiedzialni programiści nie muszą nadmiernie obciążać się ideologicznymi ograniczeniami, aby stworzyć elegancki kod.
Hari Karam Singh
14

Najlepszym rozwiązaniem, z jakim się spotkałem, jest użycie wzorca fabryki do konstruowania instancji klas. Używając wzorca, możesz zapewnić, że istnieje tylko jedno wystąpienie klasy, które jest współużytkowane przez obiekty, które go używają.

Wydawało mi się, że byłoby to skomplikowane w zarządzaniu, ale po przeczytaniu tego wpisu na blogu „Where Have All the Singletons Gone?” wydaje się takie naturalne. A na marginesie, bardzo pomaga to w izolowaniu testów jednostkowych.

Podsumowując, co musisz zrobić? Za każdym razem, gdy obiekt jest zależny od innego, otrzyma jego wystąpienie tylko za pośrednictwem swojego konstruktora (brak nowego słowa kluczowego w Twojej klasie).

class NeedyClass {

    private ExSingletonClass exSingleton;

    public NeedyClass(ExSingletonClass exSingleton){
        this.exSingleton = exSingleton;
    }

    // Here goes some code that uses the exSingleton object
}

A potem fabryka.

class FactoryOfNeedy {

    private ExSingletonClass exSingleton;

    public FactoryOfNeedy() {
        this.exSingleton = new ExSingletonClass();
    }

    public NeedyClass buildNeedy() {
        return new NeedyClass(this.exSingleton);
    }
}

Ponieważ instancję fabryki utworzysz tylko raz, zostanie utworzona pojedyncza instancja exSingleton. Za każdym razem, gdy wywołasz buildNeedy, nowa instancja NeedyClass zostanie dołączona do exSingleton.

Mam nadzieję, że to pomoże. Proszę wskazać wszelkie błędy.

morze morskie
źródło
5
aby upewnić się, że instancja Factory zostanie utworzona tylko wtedy, gdy potrzebujesz prywatnego konstruktora i zdefiniujesz metodę buildNeedy jako metodę statyczną.
Julien Grenier,
16
Julien ma rację, ta poprawka ma fundamentalną wadę, która polega na tym, że niejawnie mówisz, że możesz utworzyć instancję tylko dla jednej fabryki. Jeśli podejmiesz niezbędne środki ostrożności, aby mieć pewność, że utworzona zostanie tylko jedna fabryka (tak jak powiedział Julien), otrzymasz ... singletona! W rzeczywistości takie podejście po prostu dodaje niepotrzebną warstwę abstrakcji do singletona.
Bob Somers,
Istnieje inny sposób posiadania tylko jednej instancji. Możesz utworzyć instancję swojej fabryki tylko raz. Nie ma potrzeby wymuszania tego przez kompilator. Pomaga to również w unittestach, w których chcesz mieć inną instancję.
frast
Jest to najlepsze rozwiązanie, jakie kiedykolwiek widziałem, dla problemu, który jest błędnie rozwiązywany za pomocą singletonów - dodawanie zależności bez zaśmiecania każdego wywołania metody (a tym samym przeprojektowanie interfejsu i refaktoryzacja jego zależności). Niewielka modyfikacja jest idealna w mojej sytuacji, w której nie jest ważne, aby istniała tylko jedna instancja „singletona”, ale ważne jest, aby wszyscy domyślnie korzystali z tej samej instancji (modyfikacja jest taka: zamiast używać fabryki, używaj statycznego metody, aby zewnętrznie ustawić typowe wystąpienie i użyć tego wystąpienia podczas tworzenia).
meustrus
Zasadniczo więc zaletą tego podejścia jest to, że musisz przetasować tylko jedną instancję obiektu (fabrykę), a nie potencjalnie całą grupę obiektów (dla których nie chcesz używać Singletonów)?
Hari Karam Singh
9

Spring czy jakikolwiek inny kontener IoC radzi sobie w tym całkiem nieźle. Ponieważ klasy są tworzone i zarządzane poza samą aplikacją, kontener może tworzyć pojedyncze klasy proste i wstrzykiwać je w razie potrzeby.

Kai
źródło
Masz jakieś linki dotyczące wyżej wymienionych tematów?
CodingWithoutComments
Te frameworki wydają się specyficzne dla języka Java - czy są jakieś inne opcje specyficzne dla języka? Albo język agnostyk?
CodingBithoutComments
dla .NET: Castle Windsor castleproject.org/container/index.html Microsoft Unity msdn.microsoft.com/en-us/library/cc468366.aspx Unity to w rzeczywistości platforma wstrzykiwania zależności, ale prawdopodobnie będzie działać w tym temacie.
Erik van Brakel
1
dlaczego nie ma na to zbyt wielu głosów? IOC to idealne rozwiązanie, aby uniknąć Singletona.
Sandeep Jindal
@CodingWithoutComments Wiem, że PHP ma kontener usług Symfony. Kolesie z rubinów mają jednak inne sposoby wstrzykiwania zależności. Czy masz konkretny język, który Cię interesuje?
Bevelopper
9

Nie powinieneś wychodzić z drogi, aby uniknąć jakiegokolwiek wzoru. Użycie wzoru jest decyzją projektową lub naturalnym dopasowaniem (po prostu pasuje). Projektując system, możesz wybrać, czy chcesz użyć wzoru, czy nie. Nie należy jednak wychodzić z siebie, aby uniknąć wszystkiego, co ostatecznie jest wyborem projektowym.

Nie unikam wzoru Singleton. Albo jest to odpowiednie i używam go, albo nie jest odpowiednie i nie używam go. Uważam, że to takie proste.

Odpowiedniość (lub jej brak) Singletona zależy od sytuacji. Jest to decyzja projektowa, którą należy podjąć, a jej konsekwencje należy zrozumieć (i udokumentować).

Thomas Owens
źródło
Chciałem powiedzieć to samo, ale to nie do końca odpowiada na jego pytanie :)
Swati
2
Porozmawiaj w swojej odpowiedzi o tym, kiedy jest to właściwe, a kiedy nie. Pięknie proszę.
CodingWithoutComments
Odpowiada na pytanie. Nie mam technik unikania stosowania wzorca singleton, ponieważ nie robię wszystkiego, co w mojej mocy, aby go uniknąć i nie sądzę, aby jakikolwiek programista powinien.
Thomas Owens
Nie sądzę, aby można było uogólniać, kiedy jest to właściwe, a kiedy nie. Wszystko zależy od projektu i w dużej mierze zależy od projektu danego systemu. Nie używam „praktycznych reguł” przy takich decyzjach.
Thomas Owens
Hmm. Nie rozumiem, dlaczego ten głos został odrzucony. Odpowiedziałem na pytanie - Jakie są Twoje techniki unikania wzoru Singleton? - mówiąc, że nie mam technik, ponieważ nie wychodzę z siebie, aby uniknąć schematu. Czy przeciwnicy mogliby rozwinąć swoje rozumowanie?
Thomas Owens
5

Monostate (opisany w Agile Software Development Roberta C. Martina) jest alternatywą dla singletona. W tym wzorcu wszystkie dane klasy są statyczne, ale metody pobierające / ustawiające są niestatyczne.

Na przykład:

public class MonoStateExample
{
    private static int x;

    public int getX()
    {
        return x;
    }

    public void setX(int xVal)
    {
        x = xVal;
    }
}

public class MonoDriver
{
    public static void main(String args[])
    {
        MonoStateExample m1 = new MonoStateExample();
        m1.setX(10);

        MonoStateExample m2 = new MonoStateExample();
        if(m1.getX() == m2.getX())
        {
            //singleton behavior
        }
    }
}

Monostate zachowuje się podobnie jak singleton, ale robi to w sposób, w którym programista niekoniecznie jest świadomy faktu, że używany jest singleton.

Mark M.
źródło
To zasługuje na więcej głosów; Przyszedłem tu z Google, szukając odświeżacza MonoState!
mahemoff
@Mark Wydaje mi się, że ten projekt jest bardzo trudny do zablokowania pod względem bezpieczeństwa gwintów. Czy utworzę blokadę instancji dla każdej zmiennej statycznej w MonoStateExample, czy też utworzę jedną blokadę, z której będą korzystać wszystkie właściwości? Oba mają poważne konsekwencje, które można łatwo rozwiązać, wykonując je według wzoru Singleton.
Martin Bliss
Ten przykład jest alternatywą dla wzorca Singleton, ale nie rozwiązuje problemów związanych z wzorcem Singleton.
Sandeep Jindal
4

Singleton wzorzec istnieje, ponieważ zdarzają się sytuacje, kiedy potrzebna jest pojedynczy obiekt dostarczenie zestawu usług .

Nawet jeśli tak jest, nadal uważam podejście do tworzenia singletonów za pomocą globalnego pola statycznego / właściwości reprezentującej instancję, za niewłaściwe. Jest to niewłaściwe, ponieważ tworzy zależność w kodzie między polem statycznym a obiektem, a nie usługami, które zapewnia obiekt.

Dlatego zamiast klasycznego, pojedynczego wzorca, polecam użycie wzorca „like” usługi z obsługiwanymi kontenerami , gdzie zamiast używać singletona przez pole statyczne, uzyskujesz do niego odwołanie za pomocą metody żądającej typu usługi.

*pseudocode* currentContainer.GetServiceByObjectType(singletonType)
//Under the covers the object might be a singleton, but this is hidden to the consumer.

zamiast jednego globalnego

*pseudocode* singletonType.Instance

W ten sposób, jeśli chcesz zmienić typ obiektu z singletona na inny, będziesz mieć na to łatwy czas. Dodatkową korzyścią jest to, że nie musisz przekazywać przydziału instancji obiektów do każdej metody.

Zobacz także Inversion of Control , idea polega na tym, że wystawiając pojedyncze elementy bezpośrednio konsumentowi, tworzysz zależność między konsumentem a instancją obiektu, a nie usługami obiektowymi dostarczanymi przez obiekt.

Moim zdaniem jest to, aby w miarę możliwości ukrywać stosowanie wzorca singleton, ponieważ nie zawsze można go uniknąć lub jest to pożądane.

Pop Catalin
źródło
Nie do końca podążam za twoim przykładem obsługiwanych kontenerów. Czy masz zasoby internetowe, do których możesz się połączyć?
CodingWithoutComments
Spójrz na „Castle Project - Windsor Container” castleproject.org/container/index.html . Niestety bardzo trudno jest znaleźć abstrakcyjne publikacje na ten temat.
Pop Catalin
2

Jeśli używasz Singletona do reprezentowania pojedynczego obiektu danych, możesz zamiast tego przekazać obiekt danych jako parametr metody.

(chociaż twierdziłbym, że jest to niewłaściwy sposób używania Singletona w pierwszej kolejności)

David Koelle
źródło
1

Jeśli Twoim problemem jest to, że chcesz zachować stan, potrzebujesz klasy MumbleManager. Przed rozpoczęciem pracy z systemem klient tworzy MumbleManager, gdzie Mumble to nazwa systemu. Dzięki temu państwo zostaje zachowane. Jest szansa, że ​​Twój MumbleManager będzie zawierał torbę z majątkiem, która zawiera Twój stan.

Ten typ stylu jest bardzo podobny do C i niezbyt obiektowy - przekonasz się, że wszystkie obiekty definiujące Twój system będą miały odniesienie do tego samego MumbleManagera.

cokół
źródło
Nie opowiadając się za ani przeciw Singletonowi, muszę powiedzieć, że to rozwiązanie jest anty-wzorcem. MumbleManager staje się „klasą Boga”, która zawiera wszystkie rodzaje odmiennej wiedzy, naruszając Pojedynczą Odpowiedzialność. Równie dobrze może to być singleton dla wszystkich aplikacji.
Martin Bliss
0

Użyj zwykłego obiektu i obiektu fabrycznego. Fabryka jest odpowiedzialna za nadzorowanie instancji i zwykłych szczegółów obiektu tylko z informacjami o konfiguracji (zawiera na przykład) i zachowaniem.

David
źródło
1
Czy Factory nie jest często singletonem? Tak zwykle widzę zaimplementowane wzorce Factory.
Thomas Owens
0

Właściwie, jeśli projektujesz od zera, unikając Singeltonów, możesz nie musieć obejść się bez używania Singletonów przy użyciu zmiennych statycznych. Używając zmiennych statycznych, tworzysz również mniej więcej Singleton, jedyną różnicą jest to, że tworzysz różne instancje obiektów, jednak wewnętrznie zachowują się one tak, jakby używały Singletona.

Czy możesz podać szczegółowy przykład, gdzie używasz Singletona lub gdzie jest obecnie używany Singleton i próbujesz go uniknąć? Może to pomóc ludziom znaleźć bardziej wyszukane rozwiązanie, jak można by w ogóle poradzić sobie z tą sytuacją bez Singletona.

Przy okazji, ja osobiście nie mam problemów z singletonami i nie mogę zrozumieć problemów innych ludzi z singletonami. Nie widzę w nich nic złego. To znaczy, jeśli ich nie nadużywasz. Każda użyteczna technika może zostać nadużyta, a jeśli zostanie nadużyta, doprowadzi to do negatywnych skutków. Inną często nadużywaną techniką jest dziedziczenie. Wciąż nikt nie powiedziałby, że dziedziczenie jest czymś złym tylko dlatego, że niektórzy ludzie strasznie go nadużywają.

Mecki
źródło
0

Osobiście dla mnie o wiele bardziej rozsądnym sposobem implementacji czegoś, co zachowuje się jak singleton, jest użycie w pełni statycznej klasy (statyczne składowe, statyczne metody, statyczne właściwości). Najczęściej implementuję to w ten sposób (nie mogę wymyślić żadnych różnic w zachowaniu z punktu widzenia użytkownika)

user18834
źródło
0

Myślę, że najlepszym miejscem do nadzorowania singletona jest poziom projektowania klas. Na tym etapie powinieneś być w stanie zmapować interakcje między klasami i zobaczyć, czy coś absolutnie, zdecydowanie wymaga, aby tylko 1 wystąpienie tej klasy istniało kiedykolwiek w dowolnym momencie życia aplikacji.

Jeśli tak jest, masz singletona. Jeśli rzucasz singletony dla wygody podczas kodowania, to naprawdę powinieneś wrócić do swojego projektu i również przestać kodować te singletony :)

I tak, mam tu na myśli raczej słowo „policja” niż „unikać”. Singleton nie jest czymś, czego należy unikać (tak samo, jak zmienne goto i globalne nie są czymś, czego należy unikać). Zamiast tego powinieneś monitorować jego użycie i upewnić się, że jest to najlepsza metoda na skuteczne wykonanie tego, co chcesz.

workmad3
źródło
1
Całkowicie rozumiem, co mówisz, panie robocie. I całkowicie się z tym zgadzam. Jednak twoja odpowiedź nadal nie odpowiada konkretnie na moje pytanie. Nie chciałem, aby było to kolejne pytanie „kiedy należy używać singletona?” Interesowały mnie tylko realne alternatywy dla singletona.
CodingWithoutComments
0

Używam singletona głównie jako „kontenera metod”, bez żadnego stanu. Jeśli muszę współdzielić te metody z wieloma klasami i chcę uniknąć obciążenia związanego z tworzeniem instancji i inicjalizacją, tworzę kontekst / sesję i inicjalizuję wszystkie tam klasy; wszystko, co odnosi się do sesji, ma również dostęp do zawartego w niej „singletona”.

Manrico Corazzi
źródło
0

Ponieważ nie programowałem w środowisku intensywnie zorientowanym obiektowo (np. Java), nie jestem do końca przekonany o zawiłościach dyskusji. Ale zaimplementowałem singleton w PHP 4. Zrobiłem to jako sposób na stworzenie procedury obsługi bazy danych „czarnej skrzynki”, która jest automatycznie inicjowana i nie trzeba było przekazywać wywołań funkcji w górę iw dół w niepełnym i nieco zepsutym frameworku.

Po przeczytaniu kilku linków wzorców singletonów nie jestem do końca pewien, czy zaimplementowałbym to ponownie w taki sam sposób. To, co było naprawdę potrzebne, to wiele obiektów ze współdzieloną pamięcią (np. Rzeczywisty uchwyt bazy danych) i właśnie w to zamieniło się moje wywołanie.

Podobnie jak w przypadku większości wzorców i algorytmów, używanie singletona „tylko dlatego, że jest fajne” jest niewłaściwą rzeczą do zrobienia. Potrzebowałem połączenia naprawdę „czarnej skrzynki”, które zdarzyło się bardzo przypominać singletona. I IMO to sposób na rozwiązanie problemu: bądź świadomy wzorca, ale także spójrz na jego szerszy zakres i na jakim poziomie jego instancja musi być wyjątkowa.

staticsan
źródło
-1

Co masz na myśli, jakie mam techniki, aby tego uniknąć?

Aby tego „uniknąć”, oznacza to, że jest wiele sytuacji, z którymi się spotykam, w których wzorzec singletonowy jest naturalnie dobrze dopasowany, a zatem muszę podjąć pewne środki, aby złagodzić te sytuacje.

Ale nie ma. Nie muszę unikać wzorca singleton. Po prostu się nie pojawia.

DrPizza
źródło