Add all 23 GoF design pattern implementations (2026-06-13)

This commit is contained in:
Ankur
2026-06-13 21:44:56 +05:30
commit a5beb61425
106 changed files with 2977 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
package chain;
/** Handles CRITICAL tickets — all-hands incident response */
public class CriticalIncidentTeam extends SupportHandler {
@Override
protected boolean canHandle(SupportTicket ticket) {
return ticket.getPriority() == SupportTicket.Priority.CRITICAL;
}
@Override
protected void handle(SupportTicket ticket) {
System.out.println(" [CRITICAL TEAM] All-hands war room opened: " + ticket);
}
}

View File

@@ -0,0 +1,15 @@
package chain;
/** Handles LOW priority tickets — basic FAQ and documentation responses */
public class Level1Support extends SupportHandler {
@Override
protected boolean canHandle(SupportTicket ticket) {
return ticket.getPriority() == SupportTicket.Priority.LOW;
}
@Override
protected void handle(SupportTicket ticket) {
System.out.println(" [Level-1] Resolved with FAQ: " + ticket);
}
}

View File

@@ -0,0 +1,15 @@
package chain;
/** Handles MEDIUM priority tickets — technical troubleshooting */
public class Level2Support extends SupportHandler {
@Override
protected boolean canHandle(SupportTicket ticket) {
return ticket.getPriority() == SupportTicket.Priority.MEDIUM;
}
@Override
protected void handle(SupportTicket ticket) {
System.out.println(" [Level-2] Diagnosed and fixed: " + ticket);
}
}

View File

@@ -0,0 +1,15 @@
package chain;
/** Handles HIGH priority tickets — senior engineers */
public class Level3Support extends SupportHandler {
@Override
protected boolean canHandle(SupportTicket ticket) {
return ticket.getPriority() == SupportTicket.Priority.HIGH;
}
@Override
protected void handle(SupportTicket ticket) {
System.out.println(" [Level-3] Engineering deep-dive completed: " + ticket);
}
}

View File

@@ -0,0 +1,38 @@
package chain;
/**
* Chain of Responsibility Design Pattern — Runnable Demo
*
* A support ticket routing system where each handler
* either resolves a ticket or passes it up the chain.
*
* Run: javac chain/*.java -d out/chain && java -cp out/chain chain.Main
* Article: https://ankurm.com/chain-of-responsibility-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Chain of Responsibility Demo ===\n");
// Build the chain: L1 -> L2 -> L3 -> Critical
SupportHandler l1 = new Level1Support();
l1.setNext(new Level2Support())
.setNext(new Level3Support())
.setNext(new CriticalIncidentTeam());
SupportTicket[] tickets = {
new SupportTicket("Can't find the login button", SupportTicket.Priority.LOW),
new SupportTicket("API returns 500 on /checkout", SupportTicket.Priority.MEDIUM),
new SupportTicket("Database replication lag > 30s", SupportTicket.Priority.HIGH),
new SupportTicket("Complete payment system outage", SupportTicket.Priority.CRITICAL),
};
for (SupportTicket t : tickets) {
System.out.println("Ticket: " + t);
l1.handleRequest(t);
System.out.println();
}
System.out.println("=== Demo complete ===");
}
}

View File

@@ -0,0 +1,31 @@
package chain;
/**
* Handler interface — defines the chain contract.
* Each handler knows its next handler and can either
* handle the request itself or pass it along.
*/
public abstract class SupportHandler {
private SupportHandler next;
public SupportHandler setNext(SupportHandler next) {
this.next = next;
return next; // fluent API: h1.setNext(h2).setNext(h3)
}
// Template method: subclasses implement handle(); base manages chaining
public final void handleRequest(SupportTicket ticket) {
if (canHandle(ticket)) {
handle(ticket);
} else if (next != null) {
System.out.println(" [" + getClass().getSimpleName() + "] passing up...");
next.handleRequest(ticket);
} else {
System.out.println(" [UNHANDLED] No handler for: " + ticket);
}
}
protected abstract boolean canHandle(SupportTicket ticket);
protected abstract void handle(SupportTicket ticket);
}

View File

@@ -0,0 +1,21 @@
package chain;
public class SupportTicket {
public enum Priority { LOW, MEDIUM, HIGH, CRITICAL }
private final String description;
private final Priority priority;
public SupportTicket(String description, Priority priority) {
this.description = description;
this.priority = priority;
}
public Priority getPriority() { return priority; }
@Override
public String toString() {
return "[" + priority + "] " + description;
}
}

View File

@@ -0,0 +1,8 @@
package command;
/** Command interface: every action is an object */
public interface Command {
void execute();
void undo();
String getDescription();
}

View File

@@ -0,0 +1,28 @@
package command;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* Invoker — holds command history and triggers execute/undo.
* It doesn't know what the commands do; it just calls execute() and undo().
*/
public class CommandHistory {
private final Deque<Command> history = new ArrayDeque<>();
public void execute(Command cmd) {
cmd.execute();
history.push(cmd);
System.out.println(" Executed: " + cmd.getDescription());
}
public void undo() {
if (history.isEmpty()) {
System.out.println(" Nothing to undo.");
return;
}
Command cmd = history.pop();
cmd.undo();
System.out.println(" Undone: " + cmd.getDescription());
}
}

View File

@@ -0,0 +1,23 @@
package command;
public class DeleteCommand implements Command {
private final TextEditor editor;
private final int start;
private final int length;
private String deletedText; // saved for undo
public DeleteCommand(TextEditor editor, int start, int length) {
this.editor = editor;
this.start = start;
this.length = length;
}
@Override
public void execute() {
deletedText = editor.getText().substring(start, start + length);
editor.deleteText(start, length);
}
@Override public void undo() { editor.insertText(deletedText, start); }
@Override public String getDescription() { return "Delete " + length + " chars at " + start; }
}

View File

@@ -0,0 +1,17 @@
package command;
public class InsertCommand implements Command {
private final TextEditor editor;
private final String text;
private final int position;
public InsertCommand(TextEditor editor, String text, int position) {
this.editor = editor;
this.text = text;
this.position = position;
}
@Override public void execute() { editor.insertText(text, position); }
@Override public void undo() { editor.deleteText(position, text.length()); }
@Override public String getDescription() { return "Insert \"" + text + "\" at " + position; }
}

View File

@@ -0,0 +1,40 @@
package command;
/**
* Command Design Pattern — Runnable Demo
* Run: javac command/*.java -d out/command && java -cp out/command command.Main
* Article: https://ankurm.com/command-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Command Design Pattern Demo ===\n");
TextEditor editor = new TextEditor();
CommandHistory history = new CommandHistory();
System.out.println("Initial: " + editor);
history.execute(new InsertCommand(editor, "Hello", 0));
System.out.println("After: " + editor);
history.execute(new InsertCommand(editor, " World", 5));
System.out.println("After: " + editor);
history.execute(new DeleteCommand(editor, 5, 6));
System.out.println("After: " + editor);
System.out.println("\n-- Undo sequence --");
history.undo();
System.out.println("After undo: " + editor);
history.undo();
System.out.println("After undo: " + editor);
history.undo();
System.out.println("After undo: " + editor);
history.undo(); // nothing left
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,21 @@
package command;
/**
* Receiver — contains the actual text editing logic.
* The commands call methods on this object.
*/
public class TextEditor {
private final StringBuilder text = new StringBuilder();
public void insertText(String content, int position) {
text.insert(position, content);
}
public void deleteText(int start, int length) {
text.delete(start, start + length);
}
public String getText() { return text.toString(); }
@Override public String toString() { return "Editor[\"" + text + "\"]"; }
}

View File

@@ -0,0 +1,19 @@
package interpreter;
/**
* NonTerminalExpression — addition: left + right.
*/
public class AddExpression implements Expression {
private final Expression left;
private final Expression right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}

View File

@@ -0,0 +1,9 @@
package interpreter;
/**
* AbstractExpression — declares the interpret operation.
* All terminal and non-terminal expressions implement this.
*/
public interface Expression {
int interpret();
}

View File

@@ -0,0 +1,38 @@
package interpreter;
public class Main {
public static void main(String[] args) {
// (5 + 3) * 2 → 16
Expression expr1 = new MultiplyExpression(
new AddExpression(
new NumberExpression(5),
new NumberExpression(3)
),
new NumberExpression(2)
);
System.out.println("(5 + 3) * 2 = " + expr1.interpret());
// 10 - (4 + 2) → 4
Expression expr2 = new SubtractExpression(
new NumberExpression(10),
new AddExpression(
new NumberExpression(4),
new NumberExpression(2)
)
);
System.out.println("10 - (4 + 2) = " + expr2.interpret());
// (3 * 4) + (10 - 6) → 16
Expression expr3 = new AddExpression(
new MultiplyExpression(
new NumberExpression(3),
new NumberExpression(4)
),
new SubtractExpression(
new NumberExpression(10),
new NumberExpression(6)
)
);
System.out.println("(3 * 4) + (10 - 6) = " + expr3.interpret());
}
}

View File

@@ -0,0 +1,19 @@
package interpreter;
/**
* NonTerminalExpression — multiplication: left * right.
*/
public class MultiplyExpression implements Expression {
private final Expression left;
private final Expression right;
public MultiplyExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() * right.interpret();
}
}

View File

@@ -0,0 +1,18 @@
package interpreter;
/**
* TerminalExpression — a number literal.
* Leaf node in the AST; has no child expressions.
*/
public class NumberExpression implements Expression {
private final int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret() {
return number;
}
}

View File

@@ -0,0 +1,19 @@
package interpreter;
/**
* NonTerminalExpression — subtraction: left - right.
*/
public class SubtractExpression implements Expression {
private final Expression left;
private final Expression right;
public SubtractExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() - right.interpret();
}
}

View File

@@ -0,0 +1,14 @@
package iterator;
public class Book {
private final String title;
private final String author;
private final int year;
public Book(String title, String author, int year) {
this.title = title; this.author = author; this.year = year;
}
public String getTitle() { return title; }
public int getYear() { return year; }
@Override public String toString() { return "\"" + title + "\" by " + author + " (" + year + ")"; }
}

View File

@@ -0,0 +1,7 @@
package iterator;
/** Our custom Iterator interface (mirrors java.util.Iterator) */
public interface BookIterator {
boolean hasNext();
Book next();
}

View File

@@ -0,0 +1,57 @@
package iterator;
import java.util.ArrayList;
import java.util.List;
/**
* Aggregate — the collection. Exposes iterators without
* revealing its internal storage structure.
*/
public class BookShelf {
private final List<Book> books = new ArrayList<>();
public void addBook(Book book) { books.add(book); }
/** Standard forward iterator */
public BookIterator iterator() {
return new ForwardIterator();
}
/** Filtered iterator — only books from a specific decade */
public BookIterator iteratorByDecade(int decade) {
return new DecadeIterator(decade);
}
// --- Inner iterator implementations ---
private class ForwardIterator implements BookIterator {
private int index = 0;
@Override public boolean hasNext() { return index < books.size(); }
@Override public Book next() { return books.get(index++); }
}
private class DecadeIterator implements BookIterator {
private final int decade;
private int index = 0;
private Book nextBook;
DecadeIterator(int decade) {
this.decade = decade;
advance();
}
private void advance() {
nextBook = null;
while (index < books.size()) {
Book b = books.get(index++);
if (b.getYear() / 10 * 10 == decade) { nextBook = b; break; }
}
}
@Override public boolean hasNext() { return nextBook != null; }
@Override public Book next() {
Book b = nextBook; advance(); return b;
}
}
}

View File

@@ -0,0 +1,37 @@
package iterator;
/**
* Iterator Design Pattern — Runnable Demo
* Run: javac iterator/*.java -d out/iterator && java -cp out/iterator iterator.Main
* Article: https://ankurm.com/iterator-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Iterator Design Pattern Demo ===\n");
BookShelf shelf = new BookShelf();
shelf.addBook(new Book("Clean Code", "Robert Martin", 2008));
shelf.addBook(new Book("The Pragmatic Programmer","Andrew Hunt", 1999));
shelf.addBook(new Book("Effective Java", "Joshua Bloch", 2001));
shelf.addBook(new Book("Design Patterns", "Gang of Four", 1994));
shelf.addBook(new Book("Refactoring", "Martin Fowler", 2018));
shelf.addBook(new Book("Working Effectively with Legacy Code", "Michael Feathers", 2004));
System.out.println("-- All books (forward iterator) --");
BookIterator it = shelf.iterator();
while (it.hasNext()) {
System.out.println(" " + it.next());
}
System.out.println("\n-- Books from the 2000s --");
BookIterator it2000s = shelf.iteratorByDecade(2000);
while (it2000s.hasNext()) {
System.out.println(" " + it2000s.next());
}
System.out.println("\n-- JDK Iterable: same pattern, different vocabulary --");
System.out.println(" java.util.Iterator is our BookIterator; for-each uses it under the hood");
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,7 @@
package mediator;
/** Mediator interface — defines how colleagues communicate through the hub */
public interface ChatMediator {
void sendMessage(String message, User sender);
void addUser(User user);
}

View File

@@ -0,0 +1,27 @@
package mediator;
import java.util.ArrayList;
import java.util.List;
/**
* Concrete Mediator — the chat room that routes messages between users.
* Users talk to ChatRoom; ChatRoom talks to users. Nobody else talks to anybody.
*/
public class ChatRoom implements ChatMediator {
private final List<User> users = new ArrayList<>();
@Override
public void addUser(User user) {
users.add(user);
System.out.println(" [ChatRoom] " + user.getName() + " joined the room");
}
@Override
public void sendMessage(String message, User sender) {
for (User user : users) {
if (user != sender) { // don't echo back to sender
user.receive(message, sender.getName());
}
}
}
}

View File

@@ -0,0 +1,35 @@
package mediator;
/**
* Mediator Design Pattern — Runnable Demo
* Run: javac mediator/*.java -d out/mediator && java -cp out/mediator mediator.Main
* Article: https://ankurm.com/mediator-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Mediator Design Pattern Demo ===\n");
ChatRoom room = new ChatRoom();
User alice = new User("Alice", room);
User bob = new User("Bob", room);
User carol = new User("Carol", room);
room.addUser(alice);
room.addUser(bob);
room.addUser(carol);
System.out.println();
alice.send("Hey everyone!");
System.out.println();
bob.send("Hi Alice and Carol!");
System.out.println();
carol.send("Good morning!");
System.out.println("\n-- Connections without Mediator: " + 3 + " users need " + (3 * 2) + " direct links --");
System.out.println("-- With Mediator: " + 3 + " users each connect only to the room --");
System.out.println("-- With N users: O(N) connections instead of O(N²) --");
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,26 @@
package mediator;
/**
* Colleague — knows only the mediator, not other users.
* Sends messages through the mediator; receives via receive().
*/
public class User {
private final String name;
private final ChatMediator mediator;
public User(String name, ChatMediator mediator) {
this.name = name;
this.mediator = mediator;
}
public String getName() { return name; }
public void send(String message) {
System.out.println("[" + name + "] sends: " + message);
mediator.sendMessage(message, this);
}
public void receive(String message, String from) {
System.out.println(" [" + name + "] received from " + from + ": " + message);
}
}

View File

@@ -0,0 +1,43 @@
package memento;
/**
* Originator — the text editor whose state we're saving and restoring.
* Creates mementos and restores from them; no history management here.
*/
public class Editor {
private String content = "";
private int cursorPosition = 0;
private String selectedText = "";
public void type(String text) {
content = content.substring(0, cursorPosition) + text + content.substring(cursorPosition);
cursorPosition += text.length();
}
public void selectText(int start, int end) {
this.selectedText = content.substring(start, end);
System.out.println(" Selected: \"" + selectedText + "\"");
}
public void deleteSelection() {
content = content.replace(selectedText, "");
selectedText = "";
}
/** Create a snapshot of current state */
public EditorMemento save() {
return new EditorMemento(content, cursorPosition, selectedText);
}
/** Restore state from a snapshot */
public void restore(EditorMemento memento) {
this.content = memento.getContent();
this.cursorPosition = memento.getCursorPosition();
this.selectedText = memento.getSelectedText();
}
@Override
public String toString() {
return "Editor[\"" + content + "\", cursor=" + cursorPosition + "]";
}
}

View File

@@ -0,0 +1,28 @@
package memento;
/**
* Memento — a snapshot of the editor's state.
* Immutable: once created, the state cannot be changed.
* The Caretaker holds these; the Originator creates and restores from them.
*/
public final class EditorMemento {
private final String content;
private final int cursorPosition;
private final String selectedText;
EditorMemento(String content, int cursorPosition, String selectedText) {
this.content = content;
this.cursorPosition = cursorPosition;
this.selectedText = selectedText;
}
// Package-private: only the Editor (Originator) should read the state back
String getContent() { return content; }
int getCursorPosition() { return cursorPosition; }
String getSelectedText() { return selectedText; }
@Override
public String toString() {
return "Snapshot[content=\"" + content + "\", cursor=" + cursorPosition + "]";
}
}

View File

@@ -0,0 +1,24 @@
package memento;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* Caretaker — manages the stack of mementos.
* It does NOT open or inspect the mementos; it just stores and returns them.
*/
public class History {
private final Deque<EditorMemento> snapshots = new ArrayDeque<>();
public void save(EditorMemento memento) {
snapshots.push(memento);
System.out.println(" [History] Saved: " + memento);
}
public EditorMemento undo() {
if (snapshots.isEmpty()) return null;
return snapshots.pop();
}
public int size() { return snapshots.size(); }
}

View File

@@ -0,0 +1,38 @@
package memento;
/**
* Memento Design Pattern — Runnable Demo
* Run: javac memento/*.java -d out/memento && java -cp out/memento memento.Main
* Article: https://ankurm.com/memento-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Memento Design Pattern Demo ===\n");
Editor editor = new Editor();
History history = new History();
System.out.println("Initial: " + editor);
editor.type("Hello");
history.save(editor.save());
System.out.println("After type: " + editor);
editor.type(", World");
history.save(editor.save());
System.out.println("After type: " + editor);
editor.type("! How are you?");
System.out.println("After type: " + editor);
System.out.println("\n-- Undo --");
editor.restore(history.undo());
System.out.println("After undo: " + editor);
editor.restore(history.undo());
System.out.println("After undo: " + editor);
System.out.println("\nHistory empty: " + (history.size() == 0));
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,22 @@
package observer;
/** Fires an alert when the price changes by more than a threshold % */
public class AlertObserver implements StockObserver {
private final String name;
private final double thresholdPercent;
public AlertObserver(String name, double thresholdPercent) {
this.name = name;
this.thresholdPercent = thresholdPercent;
}
@Override
public void onPriceChanged(String ticker, double oldPrice, double newPrice) {
double change = Math.abs((newPrice - oldPrice) / oldPrice * 100);
if (change >= thresholdPercent) {
System.out.printf(" [ALERT:%s] %s moved %.1f%% — ALERT TRIGGERED%n", name, ticker, change);
} else {
System.out.printf(" [Alert:%s] %s moved %.1f%% — within threshold%n", name, ticker, change);
}
}
}

View File

@@ -0,0 +1,36 @@
package observer;
/**
* Observer Design Pattern — Runnable Demo
* Run: javac observer/*.java -d out/observer && java -cp out/observer observer.Main
* Article: https://ankurm.com/observer-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Observer Design Pattern Demo ===\n");
StockMarket aapl = new StockMarket("AAPL", 175.00);
StockObserver alert = new AlertObserver("RiskEngine", 2.0);
StockObserver alice = new PortfolioObserver("Alice", 100);
StockObserver bob = new PortfolioObserver("Bob", 50);
aapl.subscribe(alert);
aapl.subscribe(alice);
aapl.subscribe(bob);
System.out.println();
aapl.setPrice(178.50); // +2% — should trigger alert
System.out.println();
aapl.setPrice(179.00); // small move
System.out.println("\n-- Bob unsubscribes --");
aapl.unsubscribe(bob);
System.out.println();
aapl.setPrice(165.00); // big drop — Bob doesn't hear it
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,19 @@
package observer;
/** Updates portfolio value whenever a held stock changes price */
public class PortfolioObserver implements StockObserver {
private final String ownerName;
private final int sharesHeld;
public PortfolioObserver(String ownerName, int sharesHeld) {
this.ownerName = ownerName;
this.sharesHeld = sharesHeld;
}
@Override
public void onPriceChanged(String ticker, double oldPrice, double newPrice) {
double gain = (newPrice - oldPrice) * sharesHeld;
System.out.printf(" [Portfolio:%s] %s×%d P&L change: %+.2f%n",
ownerName, ticker, sharesHeld, gain);
}
}

View File

@@ -0,0 +1,36 @@
package observer;
import java.util.ArrayList;
import java.util.List;
/**
* Subject (Observable) — maintains a list of observers and notifies them
* when the stock price changes. Observers register and deregister freely.
*/
public class StockMarket {
private final String ticker;
private double price;
private final List<StockObserver> observers = new ArrayList<>();
public StockMarket(String ticker, double initialPrice) {
this.ticker = ticker;
this.price = initialPrice;
System.out.println("[Market] " + ticker + " initialised at $" + initialPrice);
}
public void subscribe(StockObserver observer) { observers.add(observer); }
public void unsubscribe(StockObserver observer) { observers.remove(observer); }
public void setPrice(double newPrice) {
double old = this.price;
this.price = newPrice;
System.out.printf("[Market] %s price: $%.2f -> $%.2f%n", ticker, old, newPrice);
notifyObservers(old, newPrice);
}
private void notifyObservers(double old, double newPrice) {
for (StockObserver o : observers) {
o.onPriceChanged(ticker, old, newPrice);
}
}
}

View File

@@ -0,0 +1,6 @@
package observer;
/** Observer interface — all subscribers implement this */
public interface StockObserver {
void onPriceChanged(String ticker, double oldPrice, double newPrice);
}

View File

@@ -0,0 +1,11 @@
package state;
public class GreenState implements TrafficLightState {
@Override public void onEnter(TrafficLight light) {
System.out.println(" [GREEN] Go. Traffic flows.");
}
@Override public void next(TrafficLight light) {
light.setState(new YellowState());
}
@Override public String getColor() { return "GREEN"; }
}

View File

@@ -0,0 +1,26 @@
package state;
/**
* State Design Pattern — Runnable Demo
* Run: javac state/*.java -d out/state && java -cp out/state state.Main
* Article: https://ankurm.com/state-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== State Design Pattern Demo ===\n");
TrafficLight light = new TrafficLight();
System.out.println("Starting state: " + light.getColor());
System.out.println("\n-- Cycling through states --");
for (int i = 0; i < 6; i++) {
light.next();
}
System.out.println("\n-- Without State pattern: a single class with if/else --");
System.out.println(" Every new state adds to every method's if-else chain.");
System.out.println(" With State: add one new class, touch nothing else.");
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,11 @@
package state;
public class RedState implements TrafficLightState {
@Override public void onEnter(TrafficLight light) {
System.out.println(" [RED] Stop. Pedestrians cross.");
}
@Override public void next(TrafficLight light) {
light.setState(new GreenState());
}
@Override public String getColor() { return "RED"; }
}

View File

@@ -0,0 +1,21 @@
package state;
/** Context — holds the current state and delegates behaviour to it */
public class TrafficLight {
private TrafficLightState state;
public TrafficLight() {
setState(new RedState());
}
public void setState(TrafficLightState newState) {
this.state = newState;
state.onEnter(this);
}
public void next() {
state.next(this);
}
public String getColor() { return state.getColor(); }
}

View File

@@ -0,0 +1,8 @@
package state;
/** State interface — each concrete state handles events differently */
public interface TrafficLightState {
void onEnter(TrafficLight light);
void next(TrafficLight light);
String getColor();
}

View File

@@ -0,0 +1,11 @@
package state;
public class YellowState implements TrafficLightState {
@Override public void onEnter(TrafficLight light) {
System.out.println(" [YELLOW] Slow down. Prepare to stop.");
}
@Override public void next(TrafficLight light) {
light.setState(new RedState());
}
@Override public String getColor() { return "YELLOW"; }
}

View File

@@ -0,0 +1,12 @@
package strategy;
public class BubbleSort implements SortStrategy {
@Override
public void sort(int[] data) {
System.out.println(" [BubbleSort] O(n²) — small arrays only");
for (int i = 0; i < data.length - 1; i++)
for (int j = 0; j < data.length - 1 - i; j++)
if (data[j] > data[j + 1]) { int t = data[j]; data[j] = data[j+1]; data[j+1] = t; }
}
@Override public String getName() { return "BubbleSort"; }
}

View File

@@ -0,0 +1,37 @@
package strategy;
import java.util.Arrays;
/**
* Strategy Design Pattern — Runnable Demo
* Run: javac strategy/*.java -d out/strategy && java -cp out/strategy strategy.Main
* Article: https://ankurm.com/strategy-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Strategy Design Pattern Demo ===\n");
int[] data = {64, 34, 25, 12, 22, 11, 90};
System.out.println("Input: " + Arrays.toString(data));
Sorter sorter = new Sorter(new BubbleSort());
System.out.println("\n-- BubbleSort --");
System.out.println("Sorted: " + Arrays.toString(sorter.sort(data)));
sorter.setStrategy(new MergeSort());
System.out.println("\n-- MergeSort --");
System.out.println("Sorted: " + Arrays.toString(sorter.sort(data)));
sorter.setStrategy(new QuickSort());
System.out.println("\n-- QuickSort --");
System.out.println("Sorted: " + Arrays.toString(sorter.sort(data)));
System.out.println("\n-- Runtime strategy selection (simulating large dataset) --");
int size = 10_000;
SortStrategy chosen = size > 1000 ? new QuickSort() : new BubbleSort();
sorter.setStrategy(chosen);
System.out.println(" Chose " + chosen.getName() + " for size=" + size);
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,30 @@
package strategy;
import java.util.Arrays;
public class MergeSort implements SortStrategy {
@Override
public void sort(int[] data) {
System.out.println(" [MergeSort] O(n log n) — stable, good for large datasets");
mergeSort(data, 0, data.length - 1);
}
private void mergeSort(int[] a, int l, int r) {
if (l >= r) return;
int m = (l + r) / 2;
mergeSort(a, l, m); mergeSort(a, m + 1, r);
merge(a, l, m, r);
}
private void merge(int[] a, int l, int m, int r) {
int[] left = Arrays.copyOfRange(a, l, m + 1);
int[] right = Arrays.copyOfRange(a, m + 1, r + 1);
int i = 0, j = 0, k = l;
while (i < left.length && j < right.length)
a[k++] = left[i] <= right[j] ? left[i++] : right[j++];
while (i < left.length) a[k++] = left[i++];
while (j < right.length) a[k++] = right[j++];
}
@Override public String getName() { return "MergeSort"; }
}

View File

@@ -0,0 +1,26 @@
package strategy;
public class QuickSort implements SortStrategy {
@Override
public void sort(int[] data) {
System.out.println(" [QuickSort] O(n log n) avg — fast in practice, not stable");
quickSort(data, 0, data.length - 1);
}
private void quickSort(int[] a, int lo, int hi) {
if (lo >= hi) return;
int p = partition(a, lo, hi);
quickSort(a, lo, p - 1);
quickSort(a, p + 1, hi);
}
private int partition(int[] a, int lo, int hi) {
int pivot = a[hi], i = lo;
for (int j = lo; j < hi; j++)
if (a[j] <= pivot) { int t = a[i]; a[i++] = a[j]; a[j] = t; }
int t = a[i]; a[i] = a[hi]; a[hi] = t;
return i;
}
@Override public String getName() { return "QuickSort"; }
}

View File

@@ -0,0 +1,7 @@
package strategy;
/** Strategy interface — all sorting algorithms implement this */
public interface SortStrategy {
void sort(int[] data);
String getName();
}

View File

@@ -0,0 +1,24 @@
package strategy;
import java.util.Arrays;
/**
* Context — uses a SortStrategy. The strategy can be swapped at runtime.
* Sorter doesn't care which algorithm is used; it just calls sort().
*/
public class Sorter {
private SortStrategy strategy;
public Sorter(SortStrategy strategy) { this.strategy = strategy; }
public void setStrategy(SortStrategy strategy) {
System.out.println(" Switching to: " + strategy.getName());
this.strategy = strategy;
}
public int[] sort(int[] data) {
int[] copy = Arrays.copyOf(data, data.length);
strategy.sort(copy);
return copy;
}
}

View File

@@ -0,0 +1,31 @@
package template;
public class ApiMigration extends DataMigration {
private final String endpoint;
public ApiMigration(String endpoint) { this.endpoint = endpoint; }
@Override protected String getSourceName() { return "API:" + endpoint; }
@Override
protected void connect() {
System.out.println(" Authenticating with API at " + endpoint + "...");
}
@Override
protected int readData() {
System.out.println(" Paginating through API responses...");
return 320;
}
@Override
protected int transformData(int rawCount) {
System.out.println(" Flattening JSON, deduplicating (" + rawCount + " records)...");
return rawCount - 15; // 15 duplicates removed
}
// Override hook: silent migrations from APIs, no email spam
@Override
protected boolean sendNotification() { return false; }
}

View File

@@ -0,0 +1,27 @@
package template;
public class CsvMigration extends DataMigration {
private final String filePath;
public CsvMigration(String filePath) { this.filePath = filePath; }
@Override protected String getSourceName() { return "CSV:" + filePath; }
@Override
protected void connect() {
System.out.println(" Opening CSV file: " + filePath);
}
@Override
protected int readData() {
System.out.println(" Parsing CSV rows...");
return 1_500; // simulated row count
}
@Override
protected int transformData(int rawCount) {
System.out.println(" Mapping CSV columns to target schema (" + rawCount + " rows)...");
return rawCount; // no rows lost
}
}

View File

@@ -0,0 +1,50 @@
package template;
/**
* Abstract Class with Template Method.
* The overall migration algorithm is fixed here; subclasses fill in
* the source-specific steps (connect, read, transform).
*/
public abstract class DataMigration {
// THE TEMPLATE METHOD — defines the fixed algorithm skeleton
public final void migrate() {
System.out.println("\n[" + getSourceName() + "] Starting migration...");
connect();
validate();
int rowCount = readData();
int transformed = transformData(rowCount);
writeData(transformed);
if (sendNotification()) {
notifyStakeholders();
}
disconnect();
System.out.println("[" + getSourceName() + "] Migration complete.\n");
}
// Abstract steps — must be implemented by each subclass
protected abstract String getSourceName();
protected abstract void connect();
protected abstract int readData();
protected abstract int transformData(int rawCount);
// Concrete steps — common to all migrations
protected void validate() {
System.out.println(" Validating schema compatibility...");
}
protected void writeData(int count) {
System.out.println(" Writing " + count + " rows to target...");
}
protected void disconnect() {
System.out.println(" Closing source connection.");
}
// Hook — subclasses may override to change behaviour
protected boolean sendNotification() { return true; }
protected void notifyStakeholders() {
System.out.println(" Sending completion email to team.");
}
}

View File

@@ -0,0 +1,24 @@
package template;
/**
* Template Method Design Pattern — Runnable Demo
* Run: javac template/*.java -d out/template && java -cp out/template template.Main
* Article: https://ankurm.com/template-method-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Template Method Design Pattern Demo ===");
DataMigration csvJob = new CsvMigration("customers_export.csv");
csvJob.migrate();
DataMigration apiJob = new ApiMigration("https://api.partner.com/v2/orders");
apiJob.migrate();
System.out.println("-- Both jobs used the same migrate() skeleton --");
System.out.println("-- Each provided its own connect/read/transform implementation --");
System.out.println("-- ApiMigration overrode the sendNotification() hook: no email --");
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,26 @@
package visitor;
/**
* Concrete Visitor 1 — calculates area for each shape type.
* Area formulas live here, not scattered across shape classes.
*/
public class AreaCalculator implements ShapeVisitor {
@Override
public void visit(Circle c) {
double area = Math.PI * c.getRadius() * c.getRadius();
System.out.printf(" Area of %s = %.2f%n", c.getName(), area);
}
@Override
public void visit(Rectangle r) {
double area = r.getWidth() * r.getHeight();
System.out.printf(" Area of %s = %.2f%n", r.getName(), area);
}
@Override
public void visit(Triangle t) {
double area = 0.5 * t.getBase() * t.getHeight();
System.out.printf(" Area of %s = %.2f%n", t.getName(), area);
}
}

View File

@@ -0,0 +1,9 @@
package visitor;
public class Circle implements Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
public double getRadius() { return radius; }
@Override public String getName() { return "Circle(r=" + radius + ")"; }
@Override public void accept(ShapeVisitor visitor) { visitor.visit(this); }
}

View File

@@ -0,0 +1,38 @@
package visitor;
import java.util.List;
/**
* Visitor Design Pattern — Runnable Demo
* Run: javac visitor/*.java -d out/visitor && java -cp out/visitor visitor.Main
* Article: https://ankurm.com/visitor-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Visitor Design Pattern Demo ===\n");
List<Shape> shapes = List.of(
new Circle(5),
new Rectangle(4, 6),
new Triangle(3, 8)
);
System.out.println("-- Area Calculator Visitor --");
ShapeVisitor areaCalc = new AreaCalculator();
for (Shape shape : shapes) {
shape.accept(areaCalc);
}
System.out.println("\n-- Perimeter Calculator Visitor --");
ShapeVisitor perimCalc = new PerimeterCalculator();
for (Shape shape : shapes) {
shape.accept(perimCalc);
}
System.out.println("\n-- Key insight --");
System.out.println("Added PerimeterCalculator without touching Shape, Circle, Rectangle, Triangle.");
System.out.println("To add another operation: write one new Visitor class.");
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,28 @@
package visitor;
/**
* Concrete Visitor 2 — calculates perimeter.
* Adding this visitor added zero code to Shape, Circle, Rectangle, Triangle.
*/
public class PerimeterCalculator implements ShapeVisitor {
@Override
public void visit(Circle c) {
double p = 2 * Math.PI * c.getRadius();
System.out.printf(" Perimeter of %s = %.2f%n", c.getName(), p);
}
@Override
public void visit(Rectangle r) {
double p = 2 * (r.getWidth() + r.getHeight());
System.out.printf(" Perimeter of %s = %.2f%n", r.getName(), p);
}
@Override
public void visit(Triangle t) {
// Simplified: assume isoceles — base + 2 equal sides approximated
double side = Math.sqrt(Math.pow(t.getBase() / 2, 2) + Math.pow(t.getHeight(), 2));
double p = t.getBase() + 2 * side;
System.out.printf(" Perimeter of %s = %.2f%n", t.getName(), p);
}
}

View File

@@ -0,0 +1,10 @@
package visitor;
public class Rectangle implements Shape {
private final double width, height;
public Rectangle(double width, double height) { this.width = width; this.height = height; }
public double getWidth() { return width; }
public double getHeight() { return height; }
@Override public String getName() { return "Rectangle(" + width + "x" + height + ")"; }
@Override public void accept(ShapeVisitor visitor) { visitor.visit(this); }
}

View File

@@ -0,0 +1,7 @@
package visitor;
/** Element interface — accepts a visitor */
public interface Shape {
void accept(ShapeVisitor visitor);
String getName();
}

View File

@@ -0,0 +1,8 @@
package visitor;
/** Visitor interface — one visit() method per concrete element type */
public interface ShapeVisitor {
void visit(Circle circle);
void visit(Rectangle rectangle);
void visit(Triangle triangle);
}

View File

@@ -0,0 +1,10 @@
package visitor;
public class Triangle implements Shape {
private final double base, height;
public Triangle(double base, double height) { this.base = base; this.height = height; }
public double getBase() { return base; }
public double getHeight() { return height; }
@Override public String getName() { return "Triangle(b=" + base + ",h=" + height + ")"; }
@Override public void accept(ShapeVisitor visitor) { visitor.visit(this); }
}