Handbooks / OOP & Design Patterns / Chapter 10
Anti-patterns and Thread-safe Singleton
7 pages · ~6 min✓ Reviewed
Builds on Code Smells and Refactoring to Patterns.
Anti-Patterns Mental Model
1. What an anti-pattern is
- Anti-pattern = a common 'solution' that looks reasonable but causes recurring harm
- Symptomwhat you see
- Causewhy it forms
- Refactorthe fix
2. The four smells
| Smell | Looks like | Signal |
|---|---|---|
| God object | UserManager: auth, email, billing, DB | thousands of lines, unrelated fields, every change touches it |
| Spaghetti | deep nesting, goto-style jumps, shared mutable state | cannot trace what calls what |
| Golden hammer |
Fixing the Four Anti-Patterns
1. God Object and Spaghetti
- God object: split by responsibility (SRP)
- Extract
AuthService,Mailer,BillingService - Inject them; never hard-wire
- Spaghetti: tests first, so the refactor is safe
- Remove shared mutable state
Untangling spaghetti
- 1Testsfirst
- 2Small functions
- 3Guard clausesless nesting
- 4LayersUI / logic / data
2. Golden Hammer and Premature Abstraction
- Choose the tool from requirements and trade-offs
Singleton as Global State
1. What a singleton is
- Private constructor + static
getInstance() - One instance per JVM (per class loader)
- Gives a global access point
java
class Config { private static final Config INSTANCE = new Config(); private Config() {} static Config getInstance() { return INSTANCE; } }
2. Why it is global state
| Problem | What goes wrong |
|---|---|
| Hidden dependency | Config.getInstance() inside methods; the signature does not show the need |
Thread-Safe Singleton Implementations
1. Simple Variants
- Eager:
private static final Foo INSTANCE = new Foo(); - Class-loading safe; built even if unused; no lazy exceptions
- Synchronized
getInstance(): lazy, but every call takes the lock
2. Double-Checked Locking
java
private static volatile Foo instance; if (instance == null) { synchronized (Foo.class) { if (instance == null) instance = new Foo(); } }
- Field must be
volatile: reordering can publish a partly built object - Needs the Java 5+ memory model
3. Holder and Enum
Testing Problems with Singletons
1. How singletons break tests
| Problem | Cause and effect |
|---|---|
| State leaks | State lives for the whole JVM; test A breaks test B, so order matters |
| No mock | getInstance() is hard-wired; stubbing DB, clock or mailer needs PowerMock or reflection |
| Parallel runs | Shared mutable state makes them flaky and non-deterministic |
2. Workarounds are smells
reset()orsetInstance()only for tests weakens the design
Anti-Patterns and Singleton Cheat Sheet
1. Smell to Fix Map
| Smell | Fix |
|---|---|
| God object | Split by SRP |
| Spaghetti | Small functions + layers |
| Golden hammer | Pick tool by requirement |
| Premature abstraction | Rule of Three |
2. Singleton Recipes
| Variant |
|---|
Check yourself
Double-checked locking uses private static Foo instance; with no volatile. What can go wrong?
- Another thread can see a non-null
instancethat is only partly constructed, because of reordering. - Fix: declare it
private static volatile Foo instance;.
Test B expects Config's default path but fails only when it runs after test A, which changed the path. Why, and what is the fix?
- The singleton keeps its state for the whole JVM, so A's change leaks into B.
- Inject a Config interface through the constructor and pass a fresh fake in each test.
Eager, synchronized getInstance(), holder: which one is both lazy and lock-free?
- The holder idiom. The Holder class loads on the first getInstance() call, and JVM class initialization is thread-safe.