TRACK A · LLD · MODULE A2 · WEEK 4

CREATIONAL
PATTERNS

Singleton · Factory Method · Abstract Factory · Builder · Prototype
01 SINGLETON
02 FACTORY METHOD
03 ABSTRACT FACTORY
04 BUILDER
05 PROTOTYPE

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 / TRIGGERPATTERNREPLACES
global state needed, exactly one instanceSingletonstatic global variables
if/else picks which class to new upFactory Methodswitch-on-type creation
multiple products must be used together consistentlyAbstract Factorymixed product families
4+ constructor params, many optionalBuildertelescoping constructors
creating object is expensive, need many similar onesPrototyperepeated expensive construction
01 · Singleton
02 · Factory Method
03 · Abstract Factory
04 · Builder
05 · Prototype
01
Singleton
REAL SYSTEM → Logging System
Ensure a class has exactly one instance and provide a global access point to it. Useful for resources that must be shared and must exist only once.
// THREAD-SAFE IMPLEMENTATION (DOUBLE-CHECKED LOCKING)
Logger.java — Thread-safe Singleton JAVA
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)
Logger.java — Enum Singleton JAVA
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.
02
Factory Method
REAL SYSTEM → Parking Lot Spot Creation
Define an interface for creating an object, but let subclasses decide which class to instantiate. Decouples the caller from the concrete class being created.
ParkingSpot Factory — Factory Method Pattern JAVA
// 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.
03
Abstract Factory
REAL SYSTEM → Snake & Ladder (Themed Game)
Provide an interface for creating families of related objects without specifying concrete classes. Swapping one factory swaps the entire product family.
GameFactory — Abstract Factory Pattern JAVA
// 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.
04
Builder
REAL SYSTEM → Chess Game (Piece Construction)
Separate the construction of a complex object from its representation. Required fields in constructor, optional fields via method chaining, validate before build, result is immutable.
ChessPiece.Builder — Builder Pattern JAVA
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.
05
Prototype
REAL SYSTEM → File System (Node Cloning)
Clone objects instead of creating from scratch. Register expensive-to-build templates in a registry, clone on demand. Deep copy = independent, shallow copy = shared state.
FileSystemItem + Registry — Prototype Pattern JAVA
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
DIMENSIONSIMPLE FACTORYFACTORY METHODABSTRACT FACTORY
GoF Pattern?No (idiom)YesYes
CreatesOne productOne productFamily of products
MechanismStatic methodSubclass inheritanceInterface injection
OCP compliancePoor (modify factory)Good (new subclass)Good (new factory)
Adding product typeModify factoryNew Creator subclassUpdate all factories
Use whenSimple, few typesProduct type variesProduct 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.
← LLD A1: SOLID + OOP + UML 📄 READ STUDY NOTES ↑ ROADMAP NEXT: LLD A3 →