Singleton
Overview
Ensure a class has a single instance and provide a global access point to it.
When to use
- A single shared resource is required (for example, config or a process-wide cache).
- You need controlled, lazy access to that resource.
Java example
public final class ConfigRegistry {
private final Properties props;
private ConfigRegistry() {
this.props = loadProps();
}
public static ConfigRegistry getInstance() {
return Holder.INSTANCE;
}
public String get(String key) {
return props.getProperty(key);
}
private static Properties loadProps() {
return new Properties();
}
private static final class Holder {
private static final ConfigRegistry INSTANCE = new ConfigRegistry();
}
}
TypeScript example
class ConfigRegistry {
private static instance: ConfigRegistry | null = null;
private readonly props: Record<string, string>;
private constructor() {
this.props = loadConfig();
}
static getInstance(): ConfigRegistry {
if (!ConfigRegistry.instance) {
ConfigRegistry.instance = new ConfigRegistry();
}
return ConfigRegistry.instance;
}
get(key: string): string | undefined {
return this.props[key];
}
}
function loadConfig(): Record<string, string> {
return { env: 'prod' };
}
Pros and cons
Pros:
- Controlled access to a single instance.
- Lazy initialization is possible.
Cons:
- Global state makes tests harder.
- Hidden dependencies can spread.
Common pitfalls
- Using Singleton as a default for shared state.
- Thread-safety issues in lazy initialization.
- Hard-coded instance creation that blocks testing.