| Lesson 5 | Singleton Design Pattern Structure |
| Objective | Study the structure of the Singleton. |
The goal of this lesson is to study the structure of the Singleton pattern—what parts it contains, how those parts interact, and why the relationships matter more than the names you choose. Singleton is often described as “simple,” but correct implementations must account for modern realities: concurrency, lifecycle boundaries (process vs. distributed), serialization, reflection, and testability.
As you read, keep one key idea in mind: Singleton is not “a magic keyword.” It is a small set of constraints: restricted construction plus a controlled access path that returns the same instance each time.
A canonical Singleton implementation has three structural components. Languages differ, but the roles remain stable.
instance, theInstance, INSTANCE), but the role is constant:
it is the single reference that all callers share.
new
(or equivalent) directly.
getInstance()) that returns the unique instance and is the only supported access path.
Modern boundary reminder: “single instance” almost always means single instance per process (or per application container). In distributed systems, each process can have its own Singleton, so it does not enforce “only one” across a fleet. If you need a single coordinator across nodes, you need distributed coordination (locks/leader election), not an in-process Singleton.
The diagram below captures the essential structure: one class plays the Singleton role, owns a single instance reference, restricts construction, and exposes an access method.
theInstance or instance) that references the unique object.
getInstance()) that returns the same instance for all callers.
private) so clients cannot create additional instances.
Many older Singleton examples focus only on “one instance” and ignore concurrency. In modern code, you should always be explicit about how the instance is created and published safely.
The following implementations preserve the same structural roles (instance storage + restricted construction + access method), but improve correctness and maintainability:
public final class SingletonService {
private SingletonService() { }
private static class Holder {
private static final SingletonService INSTANCE = new SingletonService();
}
public static SingletonService getInstance() {
return Holder.INSTANCE;
}
}
public enum AppRegistry {
INSTANCE;
public void register(String key, Object value) {
// ...
}
}
This form is frequently recommended because it is naturally single-instance, resists common serialization pitfalls, and is straightforward. It also makes the “Singleton-ness” explicit in the type system.
volatile, but more complex than needed)
public final class Singleton {
private static volatile Singleton instance;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
This is a valid structural variant in modern Java, but it is often taught more than it is used. Prefer simpler patterns unless you have a specific reason to choose this one.
Some legacy examples attempt to detect multiple constructions by setting a static flag in the constructor and throwing an exception if construction happens twice. This illustrates the idea, but it is not the best structural enforcement mechanism. The stronger rule is: prevent external construction in the first place (private constructor), then provide one controlled access method.
// Legacy illustration (avoid in production): a static flag checked during construction
static boolean instance_flag = false;
Throwing a custom exception can still be useful pedagogically (it demonstrates enforcement), and it can help detect reflection misuse in some cases. However, treat it as a supplement, not the primary enforcement mechanism.
class SingletonException extends RuntimeException {
public SingletonException() { super(); }
public SingletonException(String s) { super(s); }
}
In modern systems, “structure” includes more than fields and methods. You also need a clear answer to these questions:
A common modern pattern is: keep a single instance, but let your application assembly (composition root / DI container) own the lifecycle and injection. That preserves “one instance” while avoiding hardwired global access in every consumer.
The names in the diagram and code are not important. What matters is the relationship between parts:
a class-level instance reference, restricted construction, and a controlled access method.
Also note that structural diagrams typically show only the pattern-relevant parts of a class.
A real Singleton class will almost always include additional domain methods (for example, play() on an audio controller),
but those methods are outside the pattern’s defining structure.