Ważne jest, aby odróżnić tutaj pojedyncze instancje od wzorca projektowego Singleton .
Pojedyncze wystąpienia są po prostu rzeczywistością. Większość aplikacji jest zaprojektowana do pracy tylko z jedną konfiguracją na raz, jednym interfejsem użytkownika na raz, jednym systemem plików na raz i tak dalej. Jeśli istnieje wiele stanów lub danych do utrzymania, z pewnością chciałbyś mieć tylko jedną instancję i utrzymywać ją przy życiu tak długo, jak to możliwe.
Wzorzec projektowy Singleton jest bardzo specyficznym rodzajem pojedynczego wystąpienia, a konkretnie takim, który:
- Dostępne za pośrednictwem globalnego, statycznego pola instancji;
- Utworzony przy inicjalizacji programu lub przy pierwszym dostępie;
- Brak publicznego konstruktora (nie można utworzyć bezpośrednio);
- Nigdy jawnie zwolniony (domyślnie uwolniony po zakończeniu programu).
To właśnie z powodu tego konkretnego wyboru projektu wzór wprowadza kilka potencjalnych problemów długoterminowych:
- Niemożność korzystania z klas abstrakcyjnych lub klas interfejsów;
- Niemożność podziału na podklasy;
- Wysokie sprzężenie w aplikacji (trudne do modyfikacji);
- Trudne do przetestowania (nie można sfałszować / kpić w testach jednostkowych);
- Trudne do zrównoleglenia w przypadku stanu zmiennego (wymaga obszernego blokowania);
- i tak dalej.
Żaden z tych symptomów nie jest tak naprawdę endemiczny dla pojedynczych instancji, tylko wzór Singleton.
Co możesz zamiast tego zrobić? Po prostu nie używaj wzoru Singleton.
Cytując z pytania:
Pomysł polegał na tym, aby mieć to jedno miejsce w aplikacji, które przechowuje i synchronizuje dane, a następnie wszelkie nowe ekrany, które są otwarte, mogą po prostu odpytywać większość potrzebnych danych, bez powtarzania żądań o różne dane pomocnicze z serwera. Ciągłe żądania od serwera wymagałyby zbyt dużej przepustowości - a ja mówię o dodatkowych tysiącach rachunków za Internet tygodniowo, więc było to nie do przyjęcia.
Ta koncepcja ma swoją nazwę, jak można wskazać, ale brzmi niepewnie. To się nazywa pamięć podręczna . Jeśli chcesz się spodobać, możesz to nazwać „pamięcią podręczną offline” lub zwykłą kopią zdalnych danych.
Pamięć podręczna nie musi być singletonem. Może być konieczne pojedyncze wystąpienie, jeśli chcesz uniknąć pobierania tych samych danych dla wielu wystąpień pamięci podręcznej; ale to nie znaczy, że naprawdę musisz ujawniać wszystko wszystkim .
Pierwszą rzeczą, którą zrobię, jest rozdzielenie różnych funkcjonalnych obszarów pamięci podręcznej na osobne interfejsy. Załóżmy na przykład, że tworzysz najgorszego na świecie klona YouTube opartego na Microsoft Access:
MSAccessCache
▲
|
+ ----------------- + ----------------- +
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage Najpopularniejsza strona
Tutaj masz kilka interfejsów opisujących określone typy danych, do których dana klasa może potrzebować dostępu - media, profile użytkowników i strony statyczne (takie jak strona główna). Wszystko to jest realizowane przez jedną mega-pamięć podręczną, ale projektujesz swoje indywidualne klasy tak, aby akceptowały interfejsy, więc nie obchodzi ich, jaki mają typ instancji. Instancję fizyczną inicjujesz raz, kiedy program się uruchamia, a następnie po prostu zaczynasz przekazywać instancje (rzutowane na określony typ interfejsu) za pośrednictwem konstruktorów i właściwości publicznych.
Nawiasem mówiąc, nazywa się to „ wstrzykiwaniem zależności” ; nie musisz używać Spring ani żadnego specjalnego kontenera IoC, o ile Twój ogólny projekt klasy akceptuje jego zależności od obiektu wywołującego zamiast tworzyć go samodzielnie lub odwoływać się do stanu globalnego .
Dlaczego warto korzystać z projektowania opartego na interfejsie? Trzy powody:
Ułatwia odczyt kodu; z interfejsów można dokładnie zrozumieć, od których danych zależą klasy zależne.
Jeśli i kiedy zdasz sobie sprawę, że Microsoft Access nie był najlepszym wyborem dla zaplecza danych, możesz go zastąpić czymś lepszym - powiedzmy SQL Server.
Czy i kiedy zdajesz sobie sprawę, że SQL Server nie jest najlepszym wyborem dla mediów konkretnie można zerwać implementację bez wpływu innych części systemu . Tam właśnie wkracza prawdziwa siła abstrakcji.
Jeśli chcesz pójść o krok dalej, możesz użyć kontenera IoC (środowisko DI), takiego jak Spring (Java) lub Unity (.NET). Prawie każdy framework DI będzie zarządzał całym swoim życiem, a konkretnie pozwoli ci zdefiniować konkretną usługę jako pojedynczą instancję (często nazywając ją „singletonem”, ale to tylko dla znajomości). Zasadniczo te frameworki oszczędzają większość pracy małpy polegającej na ręcznym przekazywaniu instancji, ale nie są absolutnie konieczne. Nie potrzebujesz żadnych specjalnych narzędzi do wdrożenia tego projektu.
Ze względu na kompletność powinienem zwrócić uwagę, że powyższy projekt też nie jest idealny. Kiedy masz do czynienia z pamięcią podręczną (taką, jaka jest), powinieneś mieć całkowicie oddzielną warstwę . Innymi słowy, projekt taki jak ten:
+ - IMediaRepository
|
Pamięć podręczna (ogólna) --------------- + - IProfileRepository
▲ |
| + - IPageRepository
+ ----------------- + ----------------- +
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage Najpopularniejsza strona
Zaletą tego jest to, że nigdy nie musisz zerwać Cache
instancji, jeśli zdecydujesz się na refaktoryzację; możesz zmienić sposób przechowywania multimediów, po prostu wprowadzając alternatywną implementację IMediaRepository
. Jeśli pomyślisz o tym, jak to pasuje, zobaczysz, że wciąż tworzy tylko jedną fizyczną instancję pamięci podręcznej, więc nigdy nie musisz pobierać tych samych danych dwa razy.
Nie oznacza to, że każde oprogramowanie na świecie musi być zaprojektowane zgodnie z tymi rygorystycznymi standardami wysokiej spójności i luźnego sprzężenia; zależy to od wielkości i zakresu projektu, zespołu, budżetu, terminów itp. Ale jeśli pytasz, jaki jest najlepszy projekt (do zastosowania zamiast singletona), to jest to.
PS Jak stwierdzili inni, prawdopodobnie nie jest najlepszym pomysłem, aby klasy zależne wiedziały, że używają pamięci podręcznej - to jest szczegół implementacji, o który po prostu nie powinny się przejmować. Biorąc to pod uwagę, ogólna architektura nadal wyglądałaby bardzo podobnie do tego, co pokazano powyżej, po prostu nie odnosi się do poszczególnych interfejsów jako do pamięci podręcznych . Zamiast tego nazwałbyś je Usługi lub coś podobnego.