Design Patterns «Prev Next»

Lesson 3Singleton: intent and motivation
ObjectiveDescribe the Intent and Motivation for Singleton Patterns

Singleton Pattern Intent and Motivation

The Singleton is a creational design pattern used when a system needs exactly one instance of a class and a reliable way for collaborating objects to reach it. Conceptually, Singleton is simple: it restricts instantiation and centralizes access. Practically, it carries tradeoffs—especially around hidden dependencies, global mutable state, and testability—so modern best practice is to use it deliberately and sparingly.

Intent

The classic intent of Singleton is twofold:

  1. Guarantee a class has only one instance. This matters when multiple instances would create conflicting state or waste scarce resources. A single instance can act as the system’s “source of truth” for a narrowly-defined responsibility (for example, a process-local configuration snapshot, a metrics registry, or a device manager).
  2. Provide a controlled access point to that instance. The access point is typically a static method that returns the same instance each time. “Controlled” is important: the class prevents clients from constructing new instances directly, so the constraint is enforced by the implementation rather than by convention.

Modern guidance adds one more lens: Singleton is often a global access mechanism, which can be useful, but it can also hide dependencies. If many parts of your code “reach into” a Singleton, your design may become harder to test, refactor, and reason about.

Motivation

Singleton is motivated by problems where a system benefits from a single coordinating object:

  1. Single point of coordination: one instance manages a shared resource and keeps decisions consistent.
  2. Single point of access: clients can locate the instance without passing it through every call chain.
  3. Single instance enforcement: the design prevents accidental duplication and inconsistent state.

A classic example is an AudioDevice abstraction. Many machines expose one active audio output pathway at a time. If two AudioDevice objects compete to control the same underlying resource, you can get contention, race conditions, and unpredictable behavior. A single instance can serialize access (for example, by queuing requests) and centralize device initialization.

Important modernization: “single instance” almost always means per process (or per application container), not “one in the universe.” In distributed systems, microservices, or multi-node deployments, each process may have its own Singleton instance. If you truly need a single active coordinator across nodes, you typically need an external mechanism (leader election, distributed lock, database constraint, etc.), not an in-process Singleton.


Multiple Motivations for Singleton Design Pattern

Singleton can appear in several domains where “one coordinator” is a reasonable design choice:

  1. File and window managers: one manager coordinates access to OS-level resources and policies.
  2. Device interfaces: one A/D converter interface in a digital filter pipeline, one driver façade per device.
  3. System-wide services: one spooler coordinating print jobs, one logging sink per process, one metrics registry.

The key question is not “can I make this a Singleton?” but rather: does the system require a single authoritative coordinator, or am I using Singleton as a convenient global variable? If it’s primarily convenience, consider dependency injection (DI), passing interfaces explicitly, or using framework-managed lifecycles.

Best-practice constraints

  1. Keep Singleton responsibilities narrow: coordination and lifecycle, not “everything the app needs.”
  2. Prefer immutability: mutable global state is where most Singleton pain originates.
  3. Make dependencies explicit: even if the instance is globally reachable, design consumers to accept an interface.
  4. Ensure thread safety: if lazy initialization is used, creation must be safe under concurrency.

Question: How do we ensure that a class has only one instance and that the instance is easily accessible?
Answer: Make the class responsible for tracking its sole instance (typically via a private constructor and a static accessor), prevent external instantiation, and provide a well-defined access method.


Implementation sketch

The mechanics vary by language. The implementation below illustrates the intent: prevent direct construction and expose one instance. In production code, prefer patterns that are naturally thread-safe and test-friendly (for example, DI-managed singletons or language idioms).

// Example (Java): Initialization-on-demand holder idiom (lazy, thread-safe)
public final class TrafficLightManager {

    private TrafficLightManager() {
        // private: prevents external instantiation
    }

    private static class Holder {
        private static final TrafficLightManager INSTANCE = new TrafficLightManager();
    }

    public static TrafficLightManager getInstance() {
        return Holder.INSTANCE;
    }

    // Keep responsibilities narrow: coordination, not "all application state"
    public void synchronizeLights() {
        // coordination logic
    }
}

If you need to unit test code that depends on TrafficLightManager, consider defining an interface and injecting an implementation. That preserves the “single instance per runtime” behavior while avoiding hardwired global coupling in consumers.

Apply Singleton to Traffic Light Manager

Later in this module, you will apply Singleton to guarantee that the traffic light manager has only one instance. That single coordinator can help ensure the lights remain in sync and that scheduling decisions are consistent.

Most patterns have multiple motivations; the value of a pattern is its adaptability across contexts. The discipline is knowing when the pattern improves clarity and correctness—and when it introduces unnecessary coupling.


Singleton Role

The Singleton pattern’s role is to enforce a single instance and provide a stable access point to it. The class itself enforces the constraint (clients do not “behave correctly” by convention; the design makes incorrect construction impossible). When used well, Singleton centralizes coordination for a specific responsibility and avoids duplicate state.

Modern best practice is to treat Singleton as a lifecycle decision (one instance) rather than a global dependency shortcut. If a design starts to rely on “reaching into” the Singleton from everywhere, you will often get a cleaner architecture by switching to explicit dependency passing or a DI container that manages a singleton-scoped service.


SEMrush Software 3 SEMrush Banner 3