It's important to distinguish here between single instances and the Singleton design pattern.
Single instances are simply a reality. Most apps are only designed to work with one configuration at a time, one UI at a time, one file system at a time, and so on. If there's a lot of state or data to be maintained, then certainly you would want to have just one instance and keep it alive as long as possible.
The Singleton design pattern is a very specific type of single instance, specifically one that is:
- Accessible via a global, static instance field;
- Created either on program initialization or upon first access;
- No public constructor (cannot instantiate directly);
- Never explicitly freed (implicitly freed on program termination).
It is because of this specific design choice that the pattern introduces several potential long-term problems:
- Inability to use abstract or interface classes;
- Inability to subclass;
- High coupling across the application (difficult to modify);
- Difficult to test (can't fake/mock in unit tests);
- Difficult to parallelize in the case of mutable state (requires extensive locking);
- and so on.
None of these symptoms are actually endemic to single instances, just the Singleton pattern.
What can you do instead? Simply don't use the Singleton pattern.
Quoting from the question:
The idea was to have this one place in the app which keeps the data stored and synced, and then any new screens that are opened can just query most of what they need from there, without making repetitive requests for various supporting data from the server. Constantly requesting to the server would take too much bandwidth - and I'm talking thousands of dollars extra Internet bills per week, so that was unacceptable.
This concept has a name, as you sort of hint at but sound uncertain of. It's called a cache. If you want to get fancy you can call it an "offline cache" or just an offline copy of remote data.
A cache does not need to be a singleton. It may need to be a single instance if you want to avoid fetching the same data for multiple cache instances; but that does not mean you actually have to expose everything to everyone.
The first thing I'd do is separate out the different functional areas of the cache into separate interfaces. For example, let's say you were making the world's worst YouTube clone based on Microsoft Access:
MSAccessCache
▲
|
+-----------------+-----------------+
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage MostPopularPage
Here you have several interfaces describing the specific types of data a particular class might need access to - media, user profiles, and static pages (like the front page). All of that is implemented by one mega-cache, but you design your individual classes to accept the interfaces instead, so they don't care what kind of an instance they have. You initialize the physical instance once, when your program starts, and then just start passing around the instances (cast to a particular interface type) via constructors and public properties.
This is called Dependency Injection, by the way; you don't need to use Spring or any special IoC container, just so long as your general class design accepts its dependencies from the caller instead of instantiating them on its own or referencing global state.
Why should you use the interface-based design? Three reasons:
It makes the code easier to read; you can clearly understand from the interfaces exactly what data the dependent classes depend on.
If and when you realize that Microsoft Access wasn't the best choice for a data back-end, you can replace it with something better - let's say SQL Server.
If and when you realize that SQL Server isn't the best choice for media specifically, you can break up your implementation without affecting any other part of the system. That is where the real power of abstraction comes in.
If you want to take it one step further then you can use an IoC container (DI framework) like Spring (Java) or Unity (.NET). Almost every DI framework will do its own lifetime management and specifically allow you to define a particular service as a single instance (often calling it "singleton", but that's only for familiarity). Basically these frameworks save you most of the monkey work of manually passing around instances, but they are not strictly necessary. You do not need any special tools in order to implement this design.
For the sake of completeness, I should point out that the design above is really not ideal either. When you are dealing with a cache (as you are), you should actually have an entirely separate layer. In other words, a design like this one:
+--IMediaRepository
|
Cache (Generic)---------------+--IProfileRepository
▲ |
| +--IPageRepository
+-----------------+-----------------+
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage MostPopularPage
The benefit of this is that you never even need to break up your Cache instance if you decide to refactor; you can change how Media is stored simply by feeding it an alternate implementation of IMediaRepository. If you think about how this fits together, you will see that it still only ever creates one physical instance of a cache, so you never need to be fetching the same data twice.
None of this is to say that every single piece of software in the world needs to be architected to these exacting standards of high cohesion and loose coupling; it depends on the size and scope of the project, your team, your budget, deadlines, etc. But if you're asking what the best design is (to use in place of a singleton), then this is it.
P.S. As others have stated, it's probably not the best idea for the dependent classes to be aware that they are using a cache - that is an implementation detail they simply should never care about. That being said, the overall architecture would still look very similar to what's pictured above, you just wouldn't refer to the individual interfaces as Caches. Instead you'd name them Services or something similar.
2Singletons are effectively global variables. If you would be wary of using mutable global variables, you should be wary of using mutable global state of other kinds, such as the Singleton pattern. – Miles Rout – 2014-01-22T03:16:47.273
doesn't the Initialization On Demand Idiom solve some of the concurrency problems mentioned previously (due to lack of static members)? http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
– SnakeDoc – 2014-01-30T15:38:54.577I personally am yet to understand the who singleton issue, however ... what if the app grows much bigger? What if you suddenly want to go the multi-tiered route? If you have 1000 clients, then they will still send a whole bunch of requests before their caches fill up. Would it maybe make sense then to have a secondary tier, or a proxy? What if it goes down? I do not know what exactly you are building, but is sounds to me that you can get away with a singleton only for as long as the requirements do not change to the point that it does not help anymore. I suppose this applies to any design ... – Job – 2011-01-27T00:07:25.963
8What problem is the use of a Singleton supposed to solve? How is it better at solving that problem than the alternatives (such as a static class)? – Anon. – 2011-01-27T00:31:39.020
10@Anon: How does using a static class make the situation better. There is still tight coupling? – Loki Astari – 2011-01-27T00:33:43.690
3@Martin: I'm not suggesting it makes it "better". I'm suggesting that in most cases, a singleton is a solution in search of a problem. – Anon. – 2011-01-27T00:38:58.853
The book says it's useful to have a singleton if it would be detrimental to have multiple instances of the class. If you can imagine a situation in which it isn't for your case, then you're probably doing it wrong. What if you suddenly handle more than one database connection? – zneak – 2011-01-27T04:16:13.923
@Anon: Static classes can't have state. Given that we're talking about a cache, where exactly would a static class store its data? – Aaronaught – 2011-01-27T19:06:37.780
1@Bobby: one remark, why make it a singleton when you could simply pass a reference to the screen (via its base class) and store a single object in the main application core ? – Matthieu M. – 2011-01-27T19:57:44.200
3@Aaronaught: Wait, since when can we not have static fields in a class? – Anon. – 2011-01-27T20:04:59.073
2I suppose you're right, @Anon, but the thought of an entire cache based on static fields makes my skin crawl. Seems that no matter how hard the language designers try to steer programmers away, the programmers always end up re-inventing global variables. – Aaronaught – 2011-01-27T20:32:06.367
3@Aaronaught: Using a Singleton is using global state, it is no better than a static class in that regard. – Anon. – 2011-01-27T20:34:41.547
6@Anon: Not true. Static classes give you (almost) no control over instantiation and make multi-threading even more difficult than Singletons do (since you have to serialize access to every individual method instead of just the instance). Singletons can also at least implement an interface, which static classes cannot. Static classes certainly have their advantages, but in this case the Singleton is definitely the lesser of two considerable evils. A static class that implements any mutable state whatsoever is like a big flashing neon "WARNING: BAD DESIGN AHEAD!" sign. – Aaronaught – 2011-01-27T20:50:33.793
6@Aaronaught: If you're just synchronizing access to the singleton, then your concurrency is broken. Your thread could be interrupted just after fetching the singleton object, another thread comes on, and blam, race condition. Using a Singleton instead of a static class, in most cases, is just taking the warning signs away and thinking that solves the problem. – Anon. – 2011-01-27T20:52:59.813
2@Anon: We are talking about instance lifetimes here. The methods of a Singleton class all have an implicit guarantee that all necessary state has been initialized; a static class has no such guarantee. Every public method of a static class (with state) must make this check explicit and must also therefore use synchronization in the case of concurrency. Singletons are usually a poor design choice, but static classes with state are almost always a disaster waiting to happen. – Aaronaught – 2011-01-27T20:58:08.523
2@Aaronaught: What, are we not allowed to use static constructors now? – Anon. – 2011-01-27T21:03:00.627
3Oh for god's sake @Anon, please don't tell me you actually write programs like this. Static constructors are supposed to be used for unmanaged resources and the like. You have absolutely no control over when they execute, can't guarantee that any base class initializers have executed, and using one suppresses any
beforefieldinitoptimizations. None of these are terrible things if you know what you're doing, but if you're using them for a purpose like this then I'd say it places you pretty far outside that camp. You do not want critical initialization code running at totally random times. – Aaronaught – 2011-01-27T21:15:09.7432@Aaronaught: I don't write (production) code like this. I also don't write code using Singletons. It's largely irrelevant to a discussion on their merits. In Java, static code blocks are guaranteed to execute at class load time, which happens on first access. C# static constructors are executed the first time a member is accessed or the class is instantiated. Does not happen at a "random time" any more than
getInstance()is called at a random time. – Anon. – 2011-01-27T21:30:46.617@Anon: That is not correct. Static constructors are guaranteed to execute before any members are accessed; there are no constraints on how soon before. At least for C#, it's right there in the documentation. I wouldn't use a Singleton or a static class with state, but if I had to choose one then at least Singleton maintains a very limited degree of OO and lifetime management; the static class version is effectively writing procedural code.
– Aaronaught – 2011-01-27T21:36:45.1072
@Aaronaught: Check here, or section 10.12 of the actual language spec (the wording is the same). With regards to terrible design, I'm in the camp of making the terrible design obvious rather than sweeping it under the rug.
– Anon. – 2011-01-27T21:53:06.7932@Anon: I'm not sure what it is that you want me to look at in there, but you do realize that you are quoting an obsolete section of the documentation that has been superseded by the one I just linked to? And by your own admission, your position is that if you had to choose between two less-than-ideal options, you would choose the worse one because the flaws would be more obvious; great, that is some fine engineering sense right there. – Aaronaught – 2011-01-27T21:58:06.990
1@Aaronaught: I realize that the link I provided was out-of-date, which is why I asked you to look at the actual current language specification (which is a pain to link to), rather than the Visual Studio documentation. And no, that's not my position. My position is that the Singleton anti-pattern is generally used in a way that provides no benefits over the alternatives, and in that case, it's better to make the terrible design obvious rather than hiding it. – Anon. – 2011-01-27T22:06:06.620
1@Anon: It does provide benefits over the alternatives if your alternative is a static class. You can control access, instantiation, you can even write a conditional in the initializer to choose from a derived type if your app needs to change its behaviour based on configuration or environment. It's not the best design, but it is miles better than a static class. Singletons have a purpose, they're just largely an obsolete design. Global static classes with mutable state were never a solution, they served only to prove the old programming adage, "You can write FORTRAN in any language." – Aaronaught – 2011-01-27T22:28:32.463
@Aaronaught: It can provide benefits, you mean. See my point about generally being used in a way that doesn't provide those benefits. – Anon. – 2011-01-27T22:47:53.993