Creational patterns abstract the instantiation process. They help make a system independent of how its objects are created, composed, and represented. Each solves a different flavour of the same question: how do we create objects in a flexible, decoupled way?
01
Singleton
→ Logging System
Ensure exactly one instance exists globally and provide a single access point to it.
02
Factory Method
→ Parking Lot
Decouple object creation from usage. Let subclasses decide which class to instantiate.
03
Abstract Factory
→ Snake & Ladder
Create families of related objects that must be used together consistently.
04
Builder
→ Chess Game
Construct complex objects step-by-step. Separate construction from representation.
05
Prototype
→ File System
Clone expensive objects efficiently instead of creating from scratch each time.
// WHEN TO REACH FOR EACH PATTERN
| SMELL / TRIGGER | PATTERN | REPLACES |
| global state needed, exactly one instance | Singleton | static global variables |
| if/else picks which class to new up | Factory Method | switch-on-type creation |
| multiple products must be used together consistently | Abstract Factory | mixed product families |
| 4+ constructor params, many optional | Builder | telescoping constructors |
| creating object is expensive, need many similar ones | Prototype | repeated expensive construction |
01 · Singleton
02 · Factory Method
03 · Abstract Factory
04 · Builder
05 · Prototype
// THREAD-SAFE IMPLEMENTATION (DOUBLE-CHECKED LOCKING)
public class Logger {
// volatile: prevents CPU caching the reference across threads
private static volatile Logger instance;
private List<String> logs = new ArrayList<>();
private Logger() {} // No public constructor
public static Logger getInstance() {
if (instance == null) { // 1st check — no lock (fast path)
synchronized (Logger.class) {
if (instance == null) { // 2nd check — with lock (safe)
instance = new Logger();
}
}
}
return instance;
}
public void log(String message) {
logs.add("[" + LocalDateTime.now() + "] " + message);
}
}
// BETTER: ENUM SINGLETON (PREFERRED)
public enum Logger {
INSTANCE; // JVM guarantees: single init + thread-safe + serialisation-safe
private final List<String> logs = new ArrayList<>();
public void log(String msg) { logs.add(msg); }
public List<String> getLogs() { return Collections.unmodifiableList(logs); }
}
// Usage
Logger.INSTANCE.log("Server started");
Logger.INSTANCE.log("User 42 logged in");
✓ Use Singleton
- Logger / Audit trail
- Configuration manager
- DB connection pool
- Thread pool manager
- Cache manager
✗ Avoid Singleton
- "Just because only one now" — global state is dangerous
- When testability matters (hard to mock)
- In microservices (process-scoped only)
- When DI container can scope it instead
In production: use DI container with singleton scope (@Singleton, @Bean) instead of static getInstance(). Same behaviour — fully injectable and mockable. This satisfies DIP.
// Product interface
interface ParkingSpot {
boolean canFit(Vehicle v);
double getHourlyRate();
}
// Concrete Products
class CarSpot implements ParkingSpot {
public boolean canFit(Vehicle v) { return v.getType() == VehicleType.CAR; }
public double getHourlyRate() { return 20.0; }
}
class BikeSpot implements ParkingSpot { /* similar */ }
class TruckSpot implements ParkingSpot { /* similar */ }
// Creator — defines the FACTORY METHOD
abstract class SpotCreator {
public abstract ParkingSpot createSpot(int spotNum);
public ParkingSpot createAndRegister(int n) {
ParkingSpot spot = createSpot(n); // ← calls subclass impl
SpotRegistry.register(spot);
return spot;
}
}
// Concrete Creators — each overrides createSpot()
class CarSpotCreator extends SpotCreator {
public ParkingSpot createSpot(int n) { return new CarSpot(n); }
}
class TruckSpotCreator extends SpotCreator {
public ParkingSpot createSpot(int n) { return new TruckSpot(n); }
}
// Adding BikeSpotCreator = new class only, ZERO modification to existing code (OCP)
Tell-tale sign Factory Method is needed: if/else or switch chains that call `new ConcreteType()` based on a string or enum. The fix: one Creator subclass per product type.
// Abstract Factory interface — creates a FAMILY of products
interface GameFactory {
Board createBoard();
Dice createDice();
Snake createSnake();
Ladder createLadder();
}
// Concrete Factory 1 — Classic theme (all classic products)
class ClassicGameFactory implements GameFactory {
public Board createBoard() { return new ClassicBoard(); }
public Dice createDice() { return new ClassicDice(); }
public Snake createSnake() { return new ClassicSnake(); }
public Ladder createLadder() { return new ClassicLadder(); }
}
// Concrete Factory 2 — Digital theme (all digital products)
class DigitalGameFactory implements GameFactory {
public Board createBoard() { return new DigitalBoard(); }
public Dice createDice() { return new DigitalDice(); }
public Snake createSnake() { return new DigitalSnake(); }
public Ladder createLadder() { return new DigitalLadder(); }
}
// Client — knows NOTHING about Classic vs Digital specifics
class SnakeLadderGame {
private final Board board; private final Dice dice;
public SnakeLadderGame(GameFactory factory) {
// Swap theme by passing different factory — zero code change here
board = factory.createBoard();
dice = factory.createDice();
}
}
// One-line theme switch:
new SnakeLadderGame(new ClassicGameFactory());
new SnakeLadderGame(new DigitalGameFactory());
Abstract Factory vs Factory Method: Factory Method creates ONE product via subclassing. Abstract Factory creates a FAMILY of products via composition (inject factory object). When products must be consistent together → Abstract Factory.
class ChessPiece {
// All fields FINAL — immutable after build()
private final String type, color; // required
private final int file, rank; // required
private final boolean hasMoved; // optional
private final String sprite; // optional
private final int pointValue; // optional
private ChessPiece(Builder b) {
type = b.type; color = b.color; file = b.file; rank = b.rank;
hasMoved = b.hasMoved; sprite = b.sprite; pointValue = b.pointValue;
}
public static class Builder {
private final String type, color; // required — set in constructor
private final int file, rank;
private boolean hasMoved = false; // optional defaults
private String sprite = "default";
private int pointValue = 1;
public Builder(String type, String color, int file, int rank) {
this.type = type; this.color = color;
this.file = file; this.rank = rank;
}
// Fluent setters — each returns this for chaining
public Builder hasMoved(boolean m) { hasMoved = m; return this; }
public Builder sprite(String s) { sprite = s; return this; }
public Builder points(int p) { pointValue=p; return this; }
public ChessPiece build() {
if (file < 0 || file > 7) throw new IllegalArgumentException("Bad file");
if (rank < 0 || rank > 7) throw new IllegalArgumentException("Bad rank");
return new ChessPiece(this);
}
}
}
// Readable construction — no nulls, order of optionals doesn't matter
ChessPiece queen = new ChessPiece.Builder("QUEEN", "WHITE", 3, 0)
.points(9)
.sprite("queen_white.png")
.hasMoved(false)
.build();
Required fields go in the Builder constructor (can't miss them). Optional fields use method chaining. validate() inside build() before object is created. The product's private constructor ensures only Builder can create it.
abstract class FileSystemItem {
protected String name, path, permissions;
protected long size;
// Copy constructor — each subclass calls super(other)
protected FileSystemItem(FileSystemItem other) {
this.name = other.name; this.path = other.path;
this.size = other.size; this.permissions = other.permissions;
}
public abstract FileSystemItem clone(); // Each subclass deep-copies itself
}
class FileNode extends FileSystemItem {
private String contentType, encoding;
private FileNode(FileNode other) {
super(other);
this.contentType = other.contentType;
this.encoding = other.encoding;
}
@Override
public FileNode clone() { return new FileNode(this); }
}
// Prototype Registry — build once, clone many times
class FileTemplateRegistry {
private Map<String, FileSystemItem> templates = new HashMap<>();
public void register(String key, FileSystemItem t) { templates.put(key, t); }
public FileSystemItem get(String key) {
return templates.get(key).clone(); // Always return CLONE, never template
}
}
// Usage: create expensive template once
FileNode cfg = new FileNode("config", "/", 0, "rw-r--r--", "application/json");
registry.register("json-config", cfg);
// Cheap clones — no re-initialisation
FileNode appCfg = (FileNode) registry.get("json-config");
appCfg.setName("app-config.json"); // Independent — doesn't affect template
Deep vs Shallow: Java's Object.clone() is shallow — nested objects are shared. Always implement deep copy manually via copy constructors. Spring's prototype-scoped beans use this exact pattern.
| PATTERN |
CREATES |
MECHANISM |
SOLID ENFORCED |
TRADE-OFF |
REAL-WORLD |
| Singleton |
Single shared instance |
Private constructor + static accessor |
SRP
|
Hard to test, global state, hidden deps |
Logger, Config, ThreadPool |
| Factory Method |
One product (varies by subclass) |
Abstract creator; subclass overrides createXxx() |
OCP
DIP
|
One extra class per product type |
ParkingSpot, Notification types |
| Abstract Factory |
Family of consistent products |
Factory interface injected; swap factory = swap family |
OCP
DIP
ISP
|
Adding new product type = update all factories |
Java AWT, JDBC, Spring contexts |
| Builder |
Complex object step-by-step |
Inner Builder class; method chaining; validate on build() |
SRP
OCP
|
Doubles code volume for the object |
StringBuilder, HttpRequest, Protobuf |
| Prototype |
Clone of existing object |
clone() method + copy constructor + registry |
DIP
|
Deep copy complexity, track shallow vs deep boundaries |
Spring prototype beans, JS Object.create() |
// FACTORY FAMILY COMPARISON
| DIMENSION | SIMPLE FACTORY | FACTORY METHOD | ABSTRACT FACTORY |
| GoF Pattern? | No (idiom) | Yes | Yes |
| Creates | One product | One product | Family of products |
| Mechanism | Static method | Subclass inheritance | Interface injection |
| OCP compliance | Poor (modify factory) | Good (new subclass) | Good (new factory) |
| Adding product type | Modify factory | New Creator subclass | Update all factories |
| Use when | Simple, few types | Product type varies | Product families must be consistent |
// MINI PROJECT — ATM SYSTEM
Design a complete ATM system using all 5 Creational patterns. Each pattern is used where it naturally fits — not forced. The challenge is choosing correctly.
🔁
Singleton
TransactionLogger — single audit log across all ATM machines
🏭
Factory Method
AccountFactory — creates Savings / Current / FixedDeposit by type
🏗️
Abstract Factory
ATMFactory — Basic / Full / Premium ATM module sets
🔨
Builder
Receipt.Builder — required + optional fields, immutable result
📋
Prototype
CardTemplateRegistry — clone pre-built card templates per card type
// PROJECT STRUCTURE
atm-system/
├── singleton/
│ └── TransactionLogger.java
├── factory/
│ ├── Account.java ← interface
│ ├── SavingsAccount.java
│ ├── CurrentAccount.java
│ ├── FixedDepositAccount.java
│ └── AccountFactory.java ← abstract creator
├── abstractfactory/
│ ├── ATMFactory.java ← abstract factory interface
│ ├── BasicATMFactory.java
│ ├── FullATMFactory.java
│ └── PremiumATMFactory.java
├── builder/
│ └── Receipt.java ← with inner Builder class
├── prototype/
│ ├── CardTemplate.java
│ └── CardTemplateRegistry.java
└── ATMController.java ← wires all patterns together
Evaluation Criteria
1. Each pattern used correctly — not forced where inappropriate
2. SOLID principles maintained throughout (no new violations introduced)
3. UML class diagram covers all 5 pattern implementations
4. ATMController can: create account, log transaction, build receipt, clone card, assemble ATM
TASK 01
Pattern Recognition — 5 Scenarios
~1.5 hrs
›
Identify the correct Creational pattern for each scenario. Write 2-sentence justification.
1. A game needs 1,000 enemy soldiers. Each has complex AI
state (navigation, behaviour tree, inventory) but
they're all nearly identical.
2. A reporting system builds PDFs with required fields
(title, date, author) and 12 optional fields (logo,
watermark, footer, page numbers, custom message...).
3. An OS needs to create UI elements (Button, TextBox,
Dialog) consistently across Windows, macOS, Linux.
4. A payment system must write every transaction to a
single audit file that persists for the app's lifetime.
5. A notification service creates the right notification
object based on user's channel (EMAIL, SMS, PUSH, WEBHOOK).
Output: Pattern + 2-sentence justification for each.
TASK 02
Thread-safe ConnectionPool Singleton
~2 hrs · code
›
Implement a thread-safe Singleton ConnectionPool that manages exactly 10 database connections.
Requirements:
- Exactly 10 connections, created lazily on first getInstance()
- getConnection(): returns an available connection
→ if all 10 in use: block (wait) OR throw? You decide — justify.
- releaseConnection(conn): returns connection to pool
- Thread-safe: multiple threads calling simultaneously
- Explain in comments why your synchronisation is correct
Bonus: Add a timeout to getConnection() that throws
ConnectionTimeoutException after N milliseconds.
TASK 03
HttpRequest Builder — Immutable with Validation
~2 hrs · code
›
Implement an immutable HttpRequest class with a Builder. All fields must be final.
Required fields:
url (String) — must start with http:// or https://
method (enum) — GET, POST, PUT, DELETE
Optional fields (with defaults):
headers (Map<String,String>) default: empty map
body (String) default: null
timeoutMs (int) default: 30000
followRedirects(boolean) default: true
retryCount (int) default: 0
Validation rules (throw IllegalStateException on violation):
- URL must start with http:// or https://
- body only valid for POST or PUT requests
- timeoutMs must be > 0
- retryCount must be >= 0
Usage should look like:
HttpRequest req = new HttpRequest.Builder("https://api.com/users", POST)
.header("Authorization", "Bearer token")
.body("{\"name\": \"Ajay\"}")
.timeoutMs(5000)
.build();
TASK 04
Deep vs Shallow Clone — ShoppingCart Test
~1 hr · test
›
Demonstrate shallow clone causing shared-state bugs and deep clone fixing them.
Create:
class CartItem { String name; int quantity; double price; }
class ShoppingCart {
List<CartItem> items;
String userId;
ShoppingCart shallowClone() { ... }
ShoppingCart deepClone() { ... }
}
Test to write:
1. Create cart1 with 2 items
2. shallowClone() → cart2
3. Modify cart2.items.get(0).quantity = 99
4. Assert: cart1.items.get(0).quantity is ALSO 99 (shared state bug!)
5. deepClone() → cart3
6. Modify cart3.items.get(0).quantity = 99
7. Assert: cart1.items.get(0).quantity is UNCHANGED
Explain in comments: why does this happen? When is
shallow clone intentional? When is it a bug?
0 / 11 completed
A2 → Creational Patterns
Can implement thread-safe Singleton (DCL + Enum) from memory
Know why Enum Singleton beats DCL (serialisation, reflection safety)
Can explain Factory Method vs Simple Factory vs Abstract Factory clearly
Can implement Abstract Factory with 2 concrete factories from memory
Can implement Builder with required fields, optional chaining, and validate on build()
Understand deep vs shallow clone — can explain when each is intentional
Know which SOLID principle(s) each pattern enforces
✏️ Task 1: Pattern recognition — 5 scenarios identified correctly
✏️ Task 2: Thread-safe ConnectionPool Singleton implemented
✏️ Task 3: HttpRequest Builder with immutability + validation
✏️ Tasks 4 + Project: Deep/shallow clone test + ATM System (all 5 patterns)
NEXT MODULE
A3 — Structural Design Patterns
Adapter, Decorator, Proxy, Composite, Facade, Bridge, Flyweight — mapped to Vending Machine, Pizza Billing, Car Rental, File System, Splitwise, CricBuzz, TrueCaller. Mini Project: Splitwise + Simplify Algorithm.