Hierbei ist es wichtig, zwischen einzelnen Instanzen und dem Singleton-Entwurfsmuster zu unterscheiden .
Einzelne Instanzen sind einfach Realität. Die meisten Apps können jeweils nur mit einer Konfiguration, einer Benutzeroberfläche, einem Dateisystem usw. verwendet werden. Wenn viele Zustände oder Daten gepflegt werden müssen, möchten Sie mit Sicherheit nur eine Instanz haben und diese so lange wie möglich am Leben erhalten.
Das Singleton- Entwurfsmuster ist ein sehr spezifischer Typ einer einzelnen Instanz, und zwar:
- Zugriff über ein globales statisches Instanzfeld;
- Wird entweder bei der Programminitialisierung oder beim ersten Zugriff erstellt.
- Kein öffentlicher Konstruktor (kann nicht direkt instanziieren);
- Nie explizit freigegeben (implizit freigegeben bei Programmbeendigung).
Aufgrund dieser speziellen Designauswahl führt das Muster zu mehreren potenziellen Langzeitproblemen:
- Unfähigkeit, abstrakte oder Interface-Klassen zu verwenden;
- Unfähigkeit zur Unterklasse;
- Hohe Kopplung über die Anwendung (schwierig zu ändern);
- Schwierig zu testen (kann in Unit-Tests nicht fälschen / verspotten);
- Schwierig zu parallelisieren im Falle eines veränderlichen Zustands (erfordert eine umfangreiche Verriegelung);
- und so weiter.
Keines dieser Symptome ist tatsächlich für einzelne Instanzen endemisch, nur das Singleton-Muster.
Was können Sie stattdessen tun? Verwenden Sie einfach nicht das Singleton-Muster.
Zitat aus der Frage:
Die Idee war, diesen einen Ort in der App zu haben, an dem die Daten gespeichert und synchronisiert werden, und dann können alle neuen Bildschirme, die geöffnet werden, nur das meiste von dem abfragen, was sie von dort benötigen, ohne wiederholte Anfragen nach verschiedenen unterstützenden Daten vom Server zu stellen. Eine ständige Anfrage an den Server würde zu viel Bandbreite in Anspruch nehmen - und ich spreche von Tausenden von Dollar zusätzlichen Internetrechnungen pro Woche, was inakzeptabel war.
Dieses Konzept hat einen Namen, wie Sie andeuten, aber unsicher klingen. Es heißt Cache . Wenn Sie Lust haben, können Sie es als "Offline-Cache" oder nur als Offline-Kopie von Remote-Daten bezeichnen.
Ein Cache muss kein Singleton sein. Es kann brauchen eine einzelne Instanz sein , wenn Sie die gleichen Daten zu holen für mehrere Cache - Instanzen vermeiden möchten; das heißt aber nicht, dass man eigentlich alles allen aussetzen muss .
Das erste, was ich tun würde, ist, die verschiedenen Funktionsbereiche des Caches in separate Schnittstellen aufzuteilen. Angenommen, Sie haben den weltweit schlechtesten YouTube-Klon auf Basis von Microsoft Access erstellt:
MSAccessCache
▲
|
+ ----------------- + ----------------- +
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage MostPopularPage
Hier haben Sie mehrere Schnittstellen , die die spezifischen Datentypen beschreiben, auf die eine bestimmte Klasse möglicherweise zugreifen muss - Medien, Benutzerprofile und statische Seiten (wie die Startseite). All dies wird von einem Mega-Cache implementiert , aber Sie entwerfen Ihre einzelnen Klassen so, dass sie stattdessen die Schnittstellen akzeptieren, sodass es ihnen egal ist, welche Art von Instanz sie haben. Sie initialisieren die physische Instanz einmal, wenn Ihr Programm gestartet wird, und geben die Instanzen dann einfach über Konstruktoren und öffentliche Eigenschaften weiter (in einen bestimmten Schnittstellentyp umgewandelt).
Dies wird übrigens Abhängigkeitsinjektion genannt ; Sie müssen weder Spring noch einen speziellen IoC-Container verwenden, solange Ihr allgemeines Klassendesign die Abhängigkeiten vom Aufrufer akzeptiert, anstatt sie einzeln zu instanziieren oder auf den globalen Status zu verweisen .
Warum sollten Sie das schnittstellenbasierte Design verwenden? Drei Gründe:
Dies erleichtert das Lesen des Codes. An den Schnittstellen können Sie genau erkennen, von welchen Daten die abhängigen Klassen abhängen.
Wenn Sie feststellen, dass Microsoft Access nicht die beste Wahl für ein Daten-Back-End ist, können Sie es durch etwas Besseres ersetzen - sagen wir, SQL Server.
Falls und wenn Sie , dass SQL Server erkennen nicht die beste Wahl für die Medien ist speziell , können Sie Ihre Implementierung brechen , ohne einen anderen Teil des Systems zu beeinträchtigen . Hier kommt die wahre Kraft der Abstraktion ins Spiel.
Wenn Sie noch einen Schritt weiter gehen möchten, können Sie einen IoC-Container (DI-Framework) wie Spring (Java) oder Unity (.NET) verwenden. Fast jedes DI-Framework führt eine eigene Lebensdauerverwaltung durch und ermöglicht es Ihnen, einen bestimmten Dienst als einzelne Instanz zu definieren (häufig als "Singleton" bezeichnet, dies dient jedoch nur der Vertrautheit). Grundsätzlich ersparen Ihnen diese Frameworks die meiste Affenarbeit beim manuellen Weitergeben von Instanzen, sind jedoch nicht unbedingt erforderlich. Sie benötigen keine speziellen Werkzeuge, um dieses Design zu implementieren.
Der Vollständigkeit halber möchte ich darauf hinweisen, dass das obige Design auch wirklich nicht ideal ist. Wenn Sie mit einem Cache arbeiten (so wie Sie sind), sollten Sie eigentlich eine völlig separate Ebene haben . Mit anderen Worten, ein Design wie dieses:
+ - IMediaRepository
|
Cache (generisch) --------------- + - IProfileRepository
▲ |
| + - IPageRepository
+ ----------------- + ----------------- +
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage MostPopularPage
Dies hat den Vorteil, dass Sie Ihre Cache
Instanz nicht einmal trennen müssen, wenn Sie sich für eine Umgestaltung entscheiden. Sie können ändern, wie Medien gespeichert werden, indem Sie sie einer alternativen Implementierung von zuführen IMediaRepository
. Wenn Sie sich überlegen, wie dies zusammenpasst, werden Sie feststellen, dass immer nur eine physische Instanz eines Caches erstellt wird, sodass Sie niemals dieselben Daten zweimal abrufen müssen.
Nichts davon soll heißen, dass jedes einzelne Softwareteil auf der Welt nach diesen strengen Maßstäben hoher Kohäsion und loser Kopplung entwickelt werden muss. Dies hängt von der Größe und dem Umfang des Projekts, Ihrem Team, Ihrem Budget, den Fristen usw. ab. Wenn Sie jedoch nach dem besten Design fragen (anstelle eines Singletons), dann ist dies das Richtige.
PS Wie andere bereits gesagt haben, ist es wahrscheinlich nicht die beste Idee für die abhängigen Klassen, sich dessen bewusst zu sein, dass sie einen Cache verwenden - das ist ein Implementierungsdetail, das sie einfach nie interessieren sollten. Abgesehen davon würde die Gesamtarchitektur immer noch sehr ähnlich wie oben dargestellt aussehen. Sie würden die einzelnen Schnittstellen nur nicht als Caches bezeichnen . Stattdessen würden Sie sie Services oder ähnliches nennen.