Implementing Creational Design Patterns
1. Implementing Singleton Pattern
| Variant | Trait |
|---|---|
| Eager | Created at class load |
| Lazy | Created on first use |
| Enum | Joshua Bloch recommended; serialization-safe |
| DI-managed | Spring @Component default scope |
Example: Enum singleton
public enum Config {
INSTANCE;
private final Properties props = load();
public String get(String key) { return props.getProperty(key); }
}
Warning: Singletons are global state — prefer DI-managed singletons for testability.
2. Implementing Factory Method Pattern
Example: Subclass decides type
abstract class Dialog {
abstract Button createButton();
void render() { createButton().draw(); }
}
class WindowsDialog extends Dialog {
Button createButton() { return new WindowsButton(); }
}
3. Implementing Abstract Factory Pattern
Example: Family of products
interface UiFactory {
Button createButton();
Checkbox createCheckbox();
}
class MacFactory implements UiFactory { ... }
class WinFactory implements UiFactory { ... }
| Factory Method | Abstract Factory |
|---|---|
| One product | Family of related products |
| Inheritance | Composition |
4. Implementing Builder Pattern
Example: Fluent builder
public final class HttpRequest {
private final String url; private final String method; private final Map<String,String> headers;
private HttpRequest(Builder b) { url=b.url; method=b.method; headers=Map.copyOf(b.headers); }
public static Builder builder() { return new Builder(); }
public static class Builder {
private String url, method = "GET";
private final Map<String,String> headers = new HashMap<>();
public Builder url(String u) { this.url = u; return this; }
public Builder header(String k, String v) { headers.put(k,v); return this; }
public HttpRequest build() {
Objects.requireNonNull(url, "url");
return new HttpRequest(this);
}
}
}
5. Implementing Prototype Pattern
| Step | Java Mechanism |
|---|---|
| Clone interface | Custom copy() method (avoid Cloneable) |
| Deep vs shallow | Recursively copy mutable fields |
| Registry | Map of name → prototype |
6. Implementing Factory Pattern
Example: Simple factory
public final class ShapeFactory {
public static Shape create(String type) {
return switch (type) {
case "circle" -> new Circle();
case "square" -> new Square();
default -> throw new IllegalArgumentException(type);
};
}
}
7. Implementing Simple Factory
| Aspect | Detail |
|---|---|
| Not a GoF pattern | Idiom; central creation method |
| Use when | Few types, stable hierarchy |
| Limitation | Adding type means modifying factory (OCP cost) |
8. Using Static Factory Methods
| Method | Convention |
|---|---|
of | List.of(1,2) |
from | Instant.from(temporal) |
valueOf | Integer.valueOf("1") |
getInstance | Calendar.getInstance() |
newInstance | New each call |
9. Choosing Between Factory Patterns
| Need | Pattern |
|---|---|
| Single type, named creation | Static factory method |
| Many params/optional | Builder |
| Subclass-decided type | Factory Method |
| Family of products | Abstract Factory |
10. Implementing Object Pool Pattern
| Use Case | Example |
|---|---|
| Expensive creation | DB connections (HikariCP) |
| Limited resources | Threads (ThreadPoolExecutor) |
| Reuse | Buffer pools |
11. Implementing Registry Pattern
Example: Type registry
public class HandlerRegistry {
private final Map<String, Handler> handlers = new ConcurrentHashMap<>();
public void register(String key, Handler h) { handlers.put(key, h); }
public Handler resolve(String key) {
return Optional.ofNullable(handlers.get(key))
.orElseThrow(() -> new NoSuchElementException(key));
}
}
12. Implementing Multiton Pattern
| Aspect | Detail |
|---|---|
| Goal | Bounded set of instances by key |
| Storage | ConcurrentHashMap<Key, Instance> |
| Creation | computeIfAbsent for thread safety |
13. Implementing Dependency Injection Pattern
| Form | Pros | Cons |
|---|---|---|
| Constructor | Required deps explicit, immutable | Many params |
| Setter | Optional deps | Mutability |
| Field | Concise | Hidden deps, hard to test DISCOURAGED |
14. Understanding Dependency Injection
Without DI: ServiceA ──new── ServiceB (tight)
With DI: ServiceA ─uses─→ IServiceB ←─impl── ServiceB
Container injects ServiceB into ServiceA
| Benefit | Detail |
|---|---|
| Testability | Inject mocks |
| Flexibility | Swap implementations |
| SRP | Class focuses on its job, not wiring |
15. Using Service Locator Pattern
| vs DI | Locator | DI |
|---|---|---|
| Discovery | Pulls from locator | Pushed in |
| Visibility | Hidden deps | Explicit |
| Testability | Harder (global) | Easier |
Warning: Service Locator is often considered an anti-pattern — prefer constructor DI.
16. Understanding IoC Containers
| Container | Ecosystem |
|---|---|
| Spring | Java, JVM |
| Guice | Java, lightweight |
| Dagger | Compile-time DI (Android, Java) |
| Micronaut/Quarkus | Compile-time, fast startup |
| CDI | Jakarta EE standard |
17. Using Service Containers
| Responsibility | Detail |
|---|---|
| Bean instantiation | Reflection or compile-time |
| Wiring | Resolve constructor params |
| Scope | Singleton, prototype, request, session |
| Lifecycle | init/destroy callbacks |
18. Understanding Thread-Safe Singleton
| Approach | Trade-off |
|---|---|
| Eager init | Simple, no lazy benefit |
synchronized getter | Safe but slow |
| Double-checked locking | Fast; needs volatile |
| Holder idiom | Best balance |
| Enum | Simplest, serialization-safe |
19. Using Double-Checked Locking
Example: Correct DCL
public class Lazy {
private static volatile Lazy instance;
public static Lazy get() {
Lazy local = instance;
if (local == null) {
synchronized (Lazy.class) {
local = instance;
if (local == null) instance = local = new Lazy();
}
}
return local;
}
}
20. Using Initialization-on-Demand Holder
Example: Holder idiom
public class Singleton {
private Singleton() {}
private static class Holder { static final Singleton INSTANCE = new Singleton(); }
public static Singleton get() { return Holder.INSTANCE; }
}
Note: JVM guarantees class initialization is thread-safe and lazy.
21. Implementing Lazy Initialization
| Tool | Use |
|---|---|
Supplier<T> | Defer creation |
| Holder class | Class-level lazy |
Suppliers.memoize | Guava cached supplier |
22. Using Lazy<T> for Lazy Initialization
Example: Memoized supplier
public final class Lazy<T> {
private final Supplier<T> init;
private volatile T value;
public Lazy(Supplier<T> init) { this.init = init; }
public T get() {
T v = value;
if (v == null) synchronized (this) { if (value == null) value = v = init.get(); }
return v;
}
}
23. Using Functional Options
Example: Configurer pattern
HttpClient client = HttpClient.create(o -> o
.timeout(Duration.ofSeconds(5))
.retries(3)
.followRedirects(true));
24. Using Structs for Simple Factories
| Java Equivalent | Detail |
|---|---|
| Record | Concise immutable carrier |
| Static factory | Point.of(x,y) on the record |
25. Using Class Methods as Factories
Example: Named static creators
public final class Money {
public static Money usd(long cents) { return new Money(cents, USD); }
public static Money zero(Currency c) { return new Money(0, c); }
private Money(long cents, Currency c) { ... }
}
26. Using Companion Objects for Factories
| Language | Mechanism |
|---|---|
| Kotlin | companion object { fun create() = ... } |
| Scala | Object with same name as class |
| Java equivalent | Static factory methods |
27. Using Module Mixins for Object Creation
| Language | Mechanism |
|---|---|
| Ruby | include Module with self.create |
| Java equivalent | Default methods on factory interfaces |