CREATIONAL
PATTERNS
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?
| 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 |
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); } }
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
// 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)
// 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());
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();
// 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
| 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() |
| 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 |
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.
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
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
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.
A reporting system builds PDFs with required fields (title, date, author) and 12 optional fields (logo, watermark, footer, page numbers, custom message…).
An OS needs to create UI elements (Button, TextBox, Dialog) consistently across Windows, macOS, Linux.
A payment system must write every transaction to a single audit file that persists for the app’s lifetime.
A notification service creates the right notification object based on user’s channel (EMAIL, SMS, PUSH, WEBHOOK).
Output: Pattern + 2-sentence justification for each.
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 correctBonus: Add a timeout to getConnection() that throws ConnectionTimeoutException after N milliseconds.
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, DELETEOptional 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();
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:
- Create cart1 with 2 items
- shallowClone() → cart2
- Modify cart2.items.get(0).quantity = 99
- Assert: cart1.items.get(0).quantity is ALSO 99 (shared state bug!)
- deepClone() → cart3
- Modify cart3.items.get(0).quantity = 99
- 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?