d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
8.2 KiB
8.2 KiB
Singleton
Problem
You need exactly one instance of a class shared across the entire application — a config manager, logging service, or connection pool. Multiple instances would cause inconsistencies, resource waste, or data conflicts.
Solution
Ensure a class has only one instance and provide a global point of access to it. The class itself controls instantiation and returns the same instance on every request.
┌───────────────────┐
│ ConfigManager │
│ -_instance: static │
│ +getInstance() │
│ +get(key) │
│ +set(key, value) │
└───────────────────┘
│
│ always returns
▼ same instance
┌────────────────┐
│ { db_host: ... }│
│ { port: 8080 } │
└────────────────┘
When to Use
- There must be exactly one instance (hardware interface, thread pool, config store).
- The instance must be globally accessible.
- Lazy initialization is desired — create only when first needed.
When to Avoid
In most cases, prefer Dependency Injection over Singleton. Singletons are problematic because:
- They introduce hidden global state that makes code hard to reason about.
- They make unit testing extremely difficult — you can't substitute mocks easily.
- They create tight coupling — every consumer depends on the concrete singleton class.
- They violate the Single Responsibility Principle — the class manages its own lifecycle.
- Concurrency bugs hide in lazy initialization without careful synchronization.
Rule of thumb: If you're reaching for Singleton, first ask "Can I inject this instead?" See Dependency Injection for the preferred approach.
Pseudocode
class ConfigManager:
static instance: ConfigManager = null
field data: Map<string, string>
private constructor():
data = loadFromFile("config.json")
static method getInstance() -> ConfigManager:
if instance == null:
instance = new ConfigManager()
return instance
method get(key) -> string:
return data[key]
method set(key, value):
data[key] = value
// Usage
config = ConfigManager.getInstance()
config.set("db_host", "localhost")
// Anywhere else in the app:
same = ConfigManager.getInstance() // same object
print(same.get("db_host")) // "localhost"
Python
"""Singleton — ConfigManager using __new__."""
class ConfigManager:
"""Thread-safe singleton using __new__.
NOTE: For production code, prefer dependency injection.
This demonstrates the *pattern*, not a recommendation.
"""
_instance: "ConfigManager | None" = None
def __new__(cls) -> "ConfigManager":
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._data: dict[str, str] = {}
cls._instance._initialized = False
return cls._instance
def __init__(self) -> None:
if not self._initialized:
# Simulate loading config
self._data = {"db_host": "localhost", "port": "5432"}
self._initialized = True
print("ConfigManager initialized (this happens only once)")
def get(self, key: str, default: str = "") -> str:
return self._data.get(key, default)
def set(self, key: str, value: str) -> None:
self._data[key] = value
if __name__ == "__main__":
# First access — initializes
config1 = ConfigManager()
config1.set("db_host", "prod-db.example.com")
print(f"config1.get('db_host') = {config1.get('db_host')}")
# Second access — same instance
config2 = ConfigManager()
print(f"config2.get('db_host') = {config2.get('db_host')}")
print(f"config1 is config2: {config1 is config2}")
# ---- Why DI is better ----
print("\n--- Why Dependency Injection is preferred ---")
print("Problem: How do you test code that uses ConfigManager()?")
print(" - You can't substitute a mock without monkey-patching.")
print(" - Global state leaks between tests.")
print("Solution: Pass config as a constructor argument instead.")
print(" See dependency-injection.md for the full pattern.")
print("\n--- Singleton demo complete ---")
Go
// singleton.go — ConfigManager using sync.Once
package main
import (
"fmt"
"sync"
)
// ConfigManager holds application configuration.
type ConfigManager struct {
data map[string]string
}
var (
configInstance *ConfigManager
configOnce sync.Once
)
// GetConfigManager returns the singleton instance.
// sync.Once guarantees thread-safe, one-time initialization.
func GetConfigManager() *ConfigManager {
configOnce.Do(func() {
fmt.Println("ConfigManager initialized (this happens only once)")
configInstance = &ConfigManager{
data: map[string]string{
"db_host": "localhost",
"port": "5432",
},
}
})
return configInstance
}
func (c *ConfigManager) Get(key string) string {
return c.data[key]
}
func (c *ConfigManager) Set(key, value string) {
c.data[key] = value
}
func main() {
// First access — initializes
config1 := GetConfigManager()
config1.Set("db_host", "prod-db.example.com")
fmt.Printf("config1.Get(\"db_host\") = %s\n", config1.Get("db_host"))
// Second access — same instance
config2 := GetConfigManager()
fmt.Printf("config2.Get(\"db_host\") = %s\n", config2.Get("db_host"))
fmt.Printf("config1 == config2: %v\n", config1 == config2)
// ---- Why DI is better ----
fmt.Println("\n--- Why Dependency Injection is preferred ---")
fmt.Println("Problem: How do you test code that calls GetConfigManager()?")
fmt.Println(" - You can't substitute a mock without build tags or init tricks.")
fmt.Println(" - sync.Once means you can't reset between tests.")
fmt.Println("Solution: Accept a Config interface as a function/struct parameter.")
fmt.Println(" See dependency-injection.md for the full pattern.")
fmt.Println("\n--- Singleton demo complete ---")
}
JavaScript
// singleton.js — ConfigManager using module-scoped instance
//
// In JavaScript/Node.js, modules are cached after first evaluation,
// so exporting an instance IS effectively a singleton.
class ConfigManager {
constructor() {
// Guard against direct construction
if (ConfigManager._instance) {
return ConfigManager._instance;
}
this._data = { db_host: "localhost", port: "5432" };
ConfigManager._instance = this;
console.log("ConfigManager initialized (this happens only once)");
}
get(key) {
return this._data[key] ?? "";
}
set(key, value) {
this._data[key] = value;
}
}
// --- Demo ---
// First access — initializes
const config1 = new ConfigManager();
config1.set("db_host", "prod-db.example.com");
console.log(`config1.get("db_host") = ${config1.get("db_host")}`);
// Second access — same instance
const config2 = new ConfigManager();
console.log(`config2.get("db_host") = ${config2.get("db_host")}`);
console.log(`config1 === config2: ${config1 === config2}`);
// ---- Why DI is better ----
console.log("\n--- Why Dependency Injection is preferred ---");
console.log("Problem: How do you test code that uses new ConfigManager()?");
console.log(" - You can't substitute a mock without module mocking hacks.");
console.log(" - Singleton state leaks between test files.");
console.log("Solution: Pass config as a constructor/function argument.");
console.log(" See dependency-injection.md for the full pattern.");
console.log("\n--- Singleton demo complete ---");
Related Patterns
- Dependency Injection — prefer DI over singleton; DI achieves the same shared-instance goal without hidden global state or test difficulty.
- Flyweight — flyweight shares many instances of fine-grained objects; singleton ensures exactly one instance of a coarse-grained object.
- Facade — a facade is often implemented as a singleton providing a single entry point to a subsystem.
- Monostate — monostate (all instances share the same state) is an alternative to singleton that doesn't restrict instantiation.