Alternatywy dla wzoru singleton


27

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

  1. 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ć)
  2. 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 .


8
Wstrzykiwanie zależności ...
Falcon

1
@Falcon: Dependency Injection ... co? DI nie robi nic, aby rozwiązać ten problem, chociaż często daje wygodny sposób na ukrycie go.
pdr

@ Falcon masz na myśli pojemnik IoC? To pozwoli ci rozwiązać zależności w jednym wierszu. Np. Dependency.Resolve <IFoo> (); lub Dependency.Resolve <IFoo> () .DoSomething ();
CodeART

To dość stare pytanie, na które już udzielono odpowiedzi. Nadal uważam, że dobrze byłoby połączyć to: stackoverflow.com/questions/162042/…
TheSilverBullet

Odpowiedzi:


7

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ć.


17

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:

  • krótkotrwałe instancje lokalne, np. dla fabryki: typowe klasy fabryczne mają minimalne ilości stanu, jeśli takie istnieją, i nie ma żadnego rzeczywistego powodu, aby utrzymywać je przy życiu po spełnieniu ich celu; koszty tworzenia i usuwania klas nie stanowią problemu w 99% wszystkich rzeczywistych scenariuszy
  • przekazywanie instancji, np. dla menedżera zasobów: muszą one być długowieczne, ponieważ ładowanie zasobów jest drogie i chcesz zachować je w pamięci, ale absolutnie nie ma powodu, aby zapobiegać tworzeniu kolejnych instancji - kto wie, może warto mieć drugiego menedżera zasobów kilka miesięcy później ...

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.


3
To bardzo dobra odpowiedź, ale tak naprawdę nie pytałem, dlaczego powinienem / nie powinienem używać wzorca singletonu. Wiem, jakie problemy mogą mieć państwo globalne. W każdym razie tematem pytania było, jak mieć unikalne obiekty, a jednocześnie implementację bez pojedynczego wzorca.
Giorgio

3

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ś.


Czy masz na myśli, że podstawowa iniekcja zależności od idei jest w zasadzie pierwszym podejściem, które nakreśliłem powyżej (np. Za pomocą rejestru), w końcu podstawową ideą?
Giorgio

@Giorgio: DI zawsze zawiera taki rejestr, ale głównym punktem, który czyni go lepszym, jest to, że zamiast zapytania do rejestru, gdziekolwiek potrzebujesz obiektu, masz centralne obiekty, które są tranzytowo zapełniane, jak napisałem powyżej.
Michael Borgwardt,

@Giorgio: Łatwiej to zrozumieć na konkretnym przykładzie: załóżmy, że masz aplikację Java EE. Twoja logika frontonu opiera się na metodzie działania komponentu JSF Managed Bean. Gdy użytkownik naciśnie przycisk, kontener JSF tworzy instancję Managed Bean i wywołuje metodę akcji. Ale wcześniej komponent DI patrzy na pola klasy Managed Bean, a te, które mają pewną adnotację, są zapełniane odniesieniem do obiektu usługi zgodnie z konfiguracją DI, a te obiekty usługi mają to samo z nimi . Metoda akcji może następnie wywołać usługi.
Michael Borgwardt,

Niektóre osoby (łącznie z tobą) nie czytają uważnie pytań i źle rozumieją, o co tak naprawdę pytano.
Giorgio

1
@Giorgio: nic w pierwotnym pytaniu nie wskazywało, że znasz już wstrzykiwanie zależności. I zobacz zaktualizowaną odpowiedź.
Michael Borgwardt,

3

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:

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()

Którą przewagę, oprócz kpiny, ma DI nad używaniem singletonu? To pytanie bardzo mnie martwiło. Jeśli chodzi o rejestr i menedżera roota, czy nie są one wdrażane jako singleton czy przez DI? (W rzeczywistości kontener IoC to rejestr, prawda?)
Konrad Rudolph,

1
@KonradRudolph, z DI zależności są jawne, a obiekt nie przyjmuje żadnych założeń dotyczących sposobu dostarczania danego zasobu. W przypadku singletonów zależności są niejawne, a każde użycie zakłada, że ​​będzie tylko jeden taki obiekt.
Winston Ewert

@KonradRudolph, czy inne metody są implementowane przy użyciu Singletonów lub DI? Tak i nie. W końcu musisz przekazać obiekty lub przechowywać obiekty w stanie globalnym. Istnieją jednak subtelne różnice w tym, jak to robisz. Trzy wyżej wymienione wersje unikają używania stanu globalnego; ale sposób, w jaki kod końcowy pobiera odwołania, jest inny.
Winston Ewert

Użycie singletonów zakłada, że ​​nie ma pojedynczego obiektu, jeśli singleton implementuje interfejs (lub nawet jeśli nie) i przekazujesz instancję singletonu do metod, zamiast zapytania w dowolnym Singleton::instancemiejscu w kodzie klienta.
Konrad Rudolph

@KonradRudolph, wtedy naprawdę używasz hybrydowego podejścia Singleton / DI. Moja purystyczna strona uważa, że ​​powinieneś wybrać czyste podejście DI. Jednak większość problemów z singletonem tak naprawdę nie ma zastosowania.
Winston Ewert,

1

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.

class Factory
{
public:
    static Object* createObject(...);
};

Object* obj = Factory::createObject(...);

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.


4
Osobiście uważam to za inną implementację wzorca singletonu. W tym przypadku instancja singleton jest samą klasą. W szczególności: ma wszystkie te same wady co „prawdziwy” singleton.
Joachim Sauer

@Randall Cook: Myślę, że twoja odpowiedź oddaje mój punkt widzenia. Bardzo często czytam, że singleton jest bardzo zły i należy go za wszelką cenę unikać. Zgadzam się z tym, ale z drugiej strony uważam, że technicznie niemożliwe jest zawsze tego uniknąć. Kiedy potrzebujesz „jednego z czegoś”, musisz go gdzieś utworzyć i uzyskać do niego dostęp.
Giorgio

1

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.


0

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):

// network info
class Session {
  // ...
};

// current user connection info
class CurrentUser {
  // ...
};

// configuration upon user
class UserConfiguration {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfiguration {
  // ...
};

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):

// network info
class NetworkInfoClass {
  // ...
};

// current user connection info
class CurrentUserClass {
  // ...
};

// configuration upon user
// favorite options menues
class UserConfigurationClass {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfigurationClass {
  // ...
};

// this class is instantiated as a singleton
class SessionClass {
  public: NetworkInfoClass NetworkInfo;
  public: CurrentUserClass CurrentUser;
  public: UserConfigurationClass UserConfiguration;
  public: TerminalConfigurationClass TerminalConfiguration;

  public: static SessionClass Instance();
};

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.


0

Twoje oryginalne wymagania:

Więc mój ogólny scenariusz jest taki

  1. Potrzebuję tylko jednej instancji klasy ze względów optymalizacyjnych (nie potrzebuję> wielu obiektów fabryki) lub do wspólnego stanu (np. Fabryka wie, ile> instancji klasy A może nadal tworzyć)
  2. Potrzebuję sposobu na dostęp do tego wystąpienia f z F w różnych miejscach kodu.

Nie zgadzaj się z definicją singletonu (i tym, do czego powinieneś się później odnieść). Z GoF (moja wersja to 1995) strona 127:

Upewnij się, że klasa ma tylko jedną instancję i zapewnij globalny punkt dostępu do niej.

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.


0

Co powiesz na korzystanie z kontenera IoC? Z pewnymi myślami możesz skończyć z czymś w rodzaju:

Dependency.Resolve<IFoo>(); 

Musisz określić, która implementacja ma IFoobyć 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ć:

Dependency.Resolve<IFoo>().DoSomething();

Metoda statycznego getletu singletonowego można zastąpić:

var result = Dependency.Resolve<IFoo>().GetFoo();

Przykład dotyczy .NET, ale jestem pewien, że bardzo podobne można osiągnąć w innych językach.

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.