Sync Factory Method, Abstract Factory, Builder, Prototype to Java 25; fix post/code mismatches

This commit is contained in:
2026-06-23 11:00:02 +05:30
parent 2f684bf3d7
commit 2fbf89875b
51 changed files with 634 additions and 337 deletions

View File

@@ -1,21 +1,30 @@
package abstractfactory;
/** Client - uses the factory without knowing concrete classes */
public class Application {
private final Button button;
private final Checkbox checkbox;
public Application(GUIFactory factory) {
button = factory.createButton();
checkbox = factory.createCheckbox();
// The client holds references to the Abstract Factory and Abstract Products.
// It never imports LightButton, DarkButton, or any concrete class.
private final UIFactory factory;
private Button submitButton;
private TextField usernameField;
public Application(UIFactory factory) {
this.factory = factory; // theme is injected; client doesn't decide
}
public void buildUI() {
// Ask the factory — get consistent products from the same family
submitButton = factory.createButton();
usernameField = factory.createTextField();
}
public void render() {
button.render();
checkbox.render();
System.out.println("Rendering login screen:");
usernameField.render();
submitButton.render();
}
public void simulateClick() {
button.onClick();
public void simulateSubmit() {
submitButton.onClick();
}
}

View File

@@ -1,6 +1,7 @@
package abstractfactory;
// Every button, regardless of theme, must be able to render and handle clicks
public interface Button {
void render();
void onClick();
void render(); // draw on screen
void onClick(); // respond to a click event
}

View File

@@ -1,5 +0,0 @@
package abstractfactory;
public interface Checkbox {
void render();
}

View File

@@ -0,0 +1,11 @@
package abstractfactory;
public class DarkButton implements Button {
@Override
public void render() {
System.out.println("[Dark Button] Charcoal background, white text, no border");
}
@Override
public void onClick() {
System.out.println("[Dark Button] Bright highlight ripple effect");
}
}

View File

@@ -0,0 +1,10 @@
package abstractfactory;
public class DarkTextField implements TextField {
private String value = "";
@Override
public void render() {
System.out.println("[Dark TextField] Dark gray background, light placeholder text");
}
@Override
public String getValue() { return value; }
}

View File

@@ -0,0 +1,14 @@
package abstractfactory;
public class DarkThemeFactory implements UIFactory {
@Override
public Button createButton() {
return new DarkButton(); // Only Dark products come from this factory
}
@Override
public TextField createTextField() {
return new DarkTextField();
}
}

View File

@@ -1,7 +0,0 @@
package abstractfactory;
/** Abstract Factory - creates families of related UI components */
public interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}

View File

@@ -0,0 +1,11 @@
package abstractfactory;
public class LightButton implements Button {
@Override
public void render() {
System.out.println("[Light Button] White background, dark text, 1px gray border");
}
@Override
public void onClick() {
System.out.println("[Light Button] Subtle gray ripple effect");
}
}

View File

@@ -0,0 +1,10 @@
package abstractfactory;
public class LightTextField implements TextField {
private String value = "";
@Override
public void render() {
System.out.println("[Light TextField] White background, dark placeholder text");
}
@Override
public String getValue() { return value; }
}

View File

@@ -0,0 +1,14 @@
package abstractfactory;
public class LightThemeFactory implements UIFactory {
@Override
public Button createButton() {
return new LightButton(); // Only Light products come from this factory
}
@Override
public TextField createTextField() {
return new LightTextField();
}
}

View File

@@ -1,6 +0,0 @@
package abstractfactory;
public class MacButton implements Button {
@Override public void render() { System.out.println("[Mac] Rendering button with rounded corners"); }
@Override public void onClick() { System.out.println("[Mac] Button clicked - glow animation"); }
}

View File

@@ -1,5 +0,0 @@
package abstractfactory;
public class MacCheckbox implements Checkbox {
@Override public void render() { System.out.println("[Mac] Rendering checkbox with rounded tick box"); }
}

View File

@@ -1,6 +0,0 @@
package abstractfactory;
public class MacFactory implements GUIFactory {
@Override public Button createButton() { return new MacButton(); }
@Override public Checkbox createCheckbox() { return new MacCheckbox(); }
}

View File

@@ -1,22 +1,16 @@
package abstractfactory;
// Entry point: the ONLY place that knows the theme
public class Main {
public static void main(String[] args) {
System.out.println("=== Abstract Factory Pattern Demo ===\n");
String os = System.getProperty("os.name", "Windows").toLowerCase();
GUIFactory factory = os.contains("mac") ? new MacFactory() : new WindowsFactory();
System.out.println("Detected OS family: " + (os.contains("mac") ? "Mac" : "Windows"));
System.out.println();
// Production: read theme from config or OS preference
String theme = System.getProperty("app.theme", "light");
UIFactory factory = "dark".equals(theme) ? new DarkThemeFactory() : new LightThemeFactory();
Application app = new Application(factory);
app.buildUI();
app.render();
app.simulateClick();
System.out.println("\n--- Forcing Mac UI ---");
Application macApp = new Application(new MacFactory());
macApp.render();
macApp.simulateClick();
app.simulateSubmit();
}
}

View File

@@ -0,0 +1,7 @@
package abstractfactory;
// Every text field must be able to render and return its value
public interface TextField {
void render();
String getValue();
}

View File

@@ -0,0 +1,13 @@
package abstractfactory;
public interface UIFactory {
// Factory method for buttons — returns the abstract type, not LightButton or DarkButton
Button createButton();
// Factory method for text fields — same principle
TextField createTextField();
// Adding a new product type (e.g., Checkbox) means adding a method here
// AND implementing it in every Concrete Factory. This is the one cost of the pattern.
}

View File

@@ -1,6 +0,0 @@
package abstractfactory;
public class WindowsButton implements Button {
@Override public void render() { System.out.println("[Windows] Rendering button with square corners"); }
@Override public void onClick() { System.out.println("[Windows] Button clicked - raised border effect"); }
}

View File

@@ -1,5 +0,0 @@
package abstractfactory;
public class WindowsCheckbox implements Checkbox {
@Override public void render() { System.out.println("[Windows] Rendering checkbox with square tick box"); }
}

View File

@@ -1,6 +0,0 @@
package abstractfactory;
public class WindowsFactory implements GUIFactory {
@Override public Button createButton() { return new WindowsButton(); }
@Override public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}

View File

@@ -1,12 +0,0 @@
package builder;
/** Director knows how to build common configurations */
public class Director {
public House buildStarter(House.Builder builder) {
return builder.rooms(2).floors(1).garage(false).roofType("gabled").build();
}
public House buildLuxury(House.Builder builder) {
return builder.rooms(6).floors(3).garage(true).garden(true).pool(true).roofType("hip").build();
}
}

View File

@@ -1,43 +0,0 @@
package builder;
public class House {
private final int rooms;
private final int floors;
private final boolean hasGarage;
private final boolean hasGarden;
private final boolean hasPool;
private final String roofType;
private House(Builder builder) {
this.rooms = builder.rooms;
this.floors = builder.floors;
this.hasGarage = builder.hasGarage;
this.hasGarden = builder.hasGarden;
this.hasPool = builder.hasPool;
this.roofType = builder.roofType;
}
@Override
public String toString() {
return "House{rooms=" + rooms + ", floors=" + floors
+ ", garage=" + hasGarage + ", garden=" + hasGarden
+ ", pool=" + hasPool + ", roof='" + roofType + "'}";
}
public static class Builder {
private int rooms = 1;
private int floors = 1;
private boolean hasGarage = false;
private boolean hasGarden = false;
private boolean hasPool = false;
private String roofType = "flat";
public Builder rooms(int rooms) { this.rooms = rooms; return this; }
public Builder floors(int floors) { this.floors = floors; return this; }
public Builder garage(boolean v) { this.hasGarage = v; return this; }
public Builder garden(boolean v) { this.hasGarden = v; return this; }
public Builder pool(boolean v) { this.hasPool = v; return this; }
public Builder roofType(String roofType) { this.roofType = roofType; return this; }
public House build() { return new House(this); }
}
}

View File

@@ -0,0 +1,27 @@
package builder;
public class HttpClientConfig {
// Director method 1: a health check probe — short timeout, no retries
public HttpRequest buildHealthCheck(String baseUrl) {
return new HttpRequest.Builder()
.url(baseUrl + "/health")
.method("GET")
.timeoutMs(2_000) // fast fail for health checks
.maxRetries(0)
.build();
}
// Director method 2: a resilient POST with standard JSON headers and retries
public HttpRequest buildResilientPost(String url, String jsonBody) {
return new HttpRequest.Builder()
.url(url)
.method("POST")
.body(jsonBody)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.timeoutMs(15_000)
.maxRetries(3)
.build();
}
}

View File

@@ -0,0 +1,76 @@
package builder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public final class HttpRequest {
// All fields are final: the object is immutable after build() returns.
private final String url;
private final String method;
private final String body;
private final Map<String, String> headers;
private final int timeoutMs;
private final int maxRetries;
// Private: the only path to an HttpRequest is through Builder.build()
private HttpRequest(Builder builder) {
this.url = Objects.requireNonNull(builder.url, "URL is required");
this.method = builder.method;
this.body = builder.body;
this.headers = Collections.unmodifiableMap(new HashMap<>(builder.headers));
this.timeoutMs = builder.timeoutMs;
this.maxRetries = builder.maxRetries;
}
// Getters only — no setters, enforcing immutability
public String getUrl() { return url; }
public String getMethod() { return method; }
public String getBody() { return body; }
public Map<String, String> getHeaders() { return headers; }
public int getTimeoutMs() { return timeoutMs; }
public int getMaxRetries() { return maxRetries; }
@Override
public String toString() {
return String.format("%s %s (timeout=%dms, retries=%d, headers=%s, body=%s)",
method, url, timeoutMs, maxRetries, headers, body);
}
// -----------------------------------------------------------------------
// THE BUILDER — static inner class so it can call the private constructor
// -----------------------------------------------------------------------
public static class Builder {
// Required field — no default, must be set
private String url;
// Optional fields with sensible defaults
private String method = "GET";
private String body = null;
private Map<String, String> headers = new HashMap<>();
private int timeoutMs = 30_000; // 30 seconds
private int maxRetries = 0;
// Fluent methods: each returns 'this' so calls can be chained
public Builder url(String url) { this.url = url; return this; }
public Builder method(String method) { this.method = method; return this; }
public Builder body(String body) { this.body = body; return this; }
public Builder header(String key, String v) { headers.put(key, v); return this; }
public Builder timeoutMs(int ms) { this.timeoutMs = ms; return this; }
public Builder maxRetries(int n) { this.maxRetries = n; return this; }
// Terminal method: validates and creates the immutable HttpRequest
public HttpRequest build() {
if (url == null || url.isBlank()) {
throw new IllegalStateException("url() is required before calling build()");
}
if (maxRetries < 0) {
throw new IllegalStateException("maxRetries must be >= 0");
}
return new HttpRequest(this); // calls the private constructor
}
}
}

View File

@@ -1,20 +1,42 @@
package builder;
public class Main {
public static void main(String[] args) {
System.out.println("=== Builder Pattern Demo ===\n");
Director director = new Director();
// Simple GET — only the required field
HttpRequest ping = new HttpRequest.Builder()
.url("https://api.example.com/health")
.build();
House starter = director.buildStarter(new House.Builder());
System.out.println("Starter home : " + starter);
System.out.println("Simple GET: " + ping);
House luxury = director.buildLuxury(new House.Builder());
System.out.println("Luxury home : " + luxury);
// Full POST with authentication and retry
HttpRequest post = new HttpRequest.Builder()
.url("https://api.example.com/orders")
.method("POST")
.body("{\"item\":\"book\",\"qty\":1}")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer eyJ...")
.timeoutMs(10_000) // 10 seconds — name is self-documenting
.maxRetries(3)
.build();
// Client builds a custom house directly without the Director
House custom = new House.Builder()
.rooms(4).floors(2).garden(true).roofType("mansard").build();
System.out.println("Custom home : " + custom);
System.out.println("POST: " + post);
// build() throws if url is missing — validation at construction time
try {
HttpRequest bad = new HttpRequest.Builder().build(); // no URL!
} catch (IllegalStateException e) {
System.out.println("Caught: " + e.getMessage()); // url() is required
}
// Using the Director for the two standard presets defined above
HttpClientConfig config = new HttpClientConfig();
HttpRequest healthCheck = config.buildHealthCheck("https://api.example.com");
System.out.println("Health check: " + healthCheck);
HttpRequest order = config.buildResilientPost("https://api.example.com/orders", "{\"qty\":1}");
System.out.println("Order: " + order);
}
}

View File

@@ -1,11 +1,13 @@
package factorymethod;
public class EmailNotification implements Notification {
private final String email;
public EmailNotification(String email) { this.email = email; }
@Override
public void send(String message) {
System.out.println("Email -> " + email + ": " + message);
public void send(String recipient, String message) {
// In production: use JavaMail or Spring Mail
System.out.printf(" [EMAIL] To: %s | Body: %s%n", recipient, message);
}
@Override
public String getType() { return "EMAIL"; }
}

View File

@@ -0,0 +1,8 @@
package factorymethod;
public class EmailSender extends NotificationSender {
@Override
protected Notification createNotification() {
return new EmailNotification();
}
}

View File

@@ -1,11 +0,0 @@
package factorymethod;
public class EmailService extends NotificationService {
private final String email;
public EmailService(String email) { this.email = email; }
@Override
protected Notification createNotification() {
return new EmailNotification(email);
}
}

View File

@@ -1,17 +1,23 @@
package factorymethod;
public class Main {
public static void main(String[] args) {
System.out.println("=== Factory Method Pattern Demo ===\n");
NotificationService[] services = {
new EmailService("alice@example.com"),
new SmsService("+1-555-0123"),
new PushService("device-token-abc123")
};
// The client only knows about NotificationSender (the Creator)
NotificationSender sender;
for (NotificationService service : services) {
service.notifyUser("Your order #1042 has been shipped!");
}
sender = new EmailSender();
sender.notify("alice@example.com", "Your order has shipped!");
sender = new SmsSender();
sender.notify("+1-555-0100", "OTP: 482910");
sender = new PushSender();
sender.notify("device-token-xyz", "New message from Bob");
// Adding Slack: just swap the creator. Client code doesn't change.
sender = new SlackSender(); // new class, nothing else touched
sender.notify("#alerts", "CPU usage above 90%");
}
}

View File

@@ -1,5 +1,11 @@
package factorymethod;
public interface Notification {
void send(String message);
// Every notification must know how to deliver itself
void send(String recipient, String message);
// Used by the Creator for logging — it needs to know the type
// without knowing the concrete class
String getType();
}

View File

@@ -0,0 +1,31 @@
package factorymethod;
public abstract class NotificationSender {
// THE factory method — this is the one thing subclasses override.
// It returns a Notification (the Product interface), never a concrete class.
protected abstract Notification createNotification();
// The shared workflow — marked final so subclasses cannot change it.
// It calls createNotification() to get the right notification,
// then runs the same steps every time regardless of which channel was created.
public final void notify(String recipient, String message) {
Notification notification = createNotification(); // delegate creation
System.out.println("Sending " + notification.getType() + " notification...");
notification.send(recipient, message); // use the Product interface
System.out.println("Delivered.");
}
// Shared retry logic — works for all channels without change
public void notifyWithRetry(String recipient, String message, int maxRetries) {
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
notify(recipient, message);
return; // success — exit
} catch (Exception e) {
System.err.printf("Attempt %d failed: %s%n", attempt, e.getMessage());
}
}
throw new RuntimeException("All " + maxRetries + " attempts failed");
}
}

View File

@@ -1,11 +1,24 @@
package factorymethod;
/** Creator - declares the factory method */
public abstract class NotificationService {
protected abstract Notification createNotification();
public class NotificationService {
public void notifyUser(String message) {
Notification n = createNotification();
n.send(message);
public void send(String type, String recipient, String message) {
// Creation logic mixed with business logic
Notification notification;
if ("EMAIL".equals(type)) {
notification = new EmailNotification();
} else if ("SMS".equals(type)) {
notification = new SmsNotification();
} else if ("PUSH".equals(type)) {
notification = new PushNotification();
} else {
throw new IllegalArgumentException("Unknown type: " + type);
}
// Business logic that should never change
System.out.println("Sending " + type + " to " + recipient);
notification.send(recipient, message);
System.out.println("Delivered.");
}
}

View File

@@ -1,11 +1,13 @@
package factorymethod;
public class PushNotification implements Notification {
private final String deviceToken;
public PushNotification(String deviceToken) { this.deviceToken = deviceToken; }
@Override
public void send(String message) {
System.out.println("Push -> " + deviceToken + ": " + message);
public void send(String recipient, String message) {
// In production: use Firebase FCM
System.out.printf(" [PUSH] To: %s | Alert: %s%n", recipient, message);
}
@Override
public String getType() { return "PUSH"; }
}

View File

@@ -0,0 +1,8 @@
package factorymethod;
public class PushSender extends NotificationSender {
@Override
protected Notification createNotification() {
return new PushNotification();
}
}

View File

@@ -1,11 +0,0 @@
package factorymethod;
public class PushService extends NotificationService {
private final String token;
public PushService(String token) { this.token = token; }
@Override
protected Notification createNotification() {
return new PushNotification(token);
}
}

View File

@@ -0,0 +1,11 @@
package factorymethod;
// New Concrete Product — knows how to send to Slack
public class SlackNotification implements Notification {
@Override
public void send(String recipient, String message) {
System.out.printf(" [SLACK] Channel: %s | Message: %s%n", recipient, message);
}
@Override
public String getType() { return "SLACK"; }
}

View File

@@ -0,0 +1,9 @@
package factorymethod;
// New Concrete Creator — knows to create a SlackNotification
public class SlackSender extends NotificationSender {
@Override
protected Notification createNotification() {
return new SlackNotification();
}
}

View File

@@ -1,11 +1,13 @@
package factorymethod;
public class SmsNotification implements Notification {
private final String phone;
public SmsNotification(String phone) { this.phone = phone; }
@Override
public void send(String message) {
System.out.println("SMS -> " + phone + ": " + message);
public void send(String recipient, String message) {
// In production: use Twilio SDK
System.out.printf(" [SMS] To: %s | Text: %s%n", recipient, message);
}
@Override
public String getType() { return "SMS"; }
}

View File

@@ -0,0 +1,8 @@
package factorymethod;
public class SmsSender extends NotificationSender {
@Override
protected Notification createNotification() {
return new SmsNotification();
}
}

View File

@@ -1,11 +0,0 @@
package factorymethod;
public class SmsService extends NotificationService {
private final String phone;
public SmsService(String phone) { this.phone = phone; }
@Override
protected Notification createNotification() {
return new SmsNotification(phone);
}
}

View File

@@ -1,24 +0,0 @@
package prototype;
public class Circle extends Shape {
private int radius;
public Circle(int radius, String color) {
this.radius = radius;
this.color = color;
}
/** Copy constructor used by clone() */
private Circle(Circle source) {
super(source);
this.radius = source.radius;
}
@Override
public Circle clone() { return new Circle(this); }
@Override
public String toString() {
return "Circle{color=" + color + ", radius=" + radius + ", pos=(" + x + "," + y + ")}";
}
}

View File

@@ -0,0 +1,8 @@
package prototype;
// Our Prototype interface: any class that can clone itself implements this.
// The return type T allows each implementing class to return its own type,
// not the raw interface type, which makes the client code cleaner.
public interface Copyable<T> {
T copy();
}

View File

@@ -0,0 +1,46 @@
package prototype;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Document implements Copyable<Document> {
private String title; // String: immutable, safe to share on copy
private String content; // String: immutable, safe to share on copy
private List<String> tags; // List: mutable, MUST be deep-copied
private DocumentMetadata metadata; // Mutable object, MUST be deep-copied
public Document(String title, String content, List<String> tags, DocumentMetadata metadata) {
this.title = title;
this.content = content;
this.tags = new ArrayList<>(tags); // defensive copy on construction
this.metadata = metadata;
}
// Copy constructor: the preferred Prototype implementation in modern Java
public Document(Document source) {
this.title = source.title; // immutable: share
this.content = source.content; // immutable: share
this.tags = new ArrayList<>(source.tags); // mutable: new list
this.metadata = source.metadata.copy(); // mutable: deep copy via Copyable
}
@Override
public Document copy() {
return new Document(this); // delegates to copy constructor
}
// Getters and setters
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public List<String> getTags() { return Collections.unmodifiableList(tags); }
public void addTag(String tag) { tags.add(tag); }
public DocumentMetadata getMetadata() { return metadata; }
@Override public String toString() {
return String.format("Document{title='%s', tags=%s, %s}", title, tags, metadata);
}
}

View File

@@ -0,0 +1,39 @@
package prototype;
import java.time.LocalDate;
public class DocumentMetadata implements Copyable<DocumentMetadata> {
private String author;
private LocalDate createdDate; // LocalDate is immutable — safe to share
private String version;
public DocumentMetadata(String author, LocalDate createdDate, String version) {
this.author = author;
this.createdDate = createdDate; // safe: LocalDate is immutable
this.version = version;
}
// Copy constructor: creates a new instance with the same values
public DocumentMetadata(DocumentMetadata source) {
this.author = source.author; // String: immutable, safe to share
this.createdDate = source.createdDate; // LocalDate: immutable, safe to share
this.version = source.version;
}
@Override
public DocumentMetadata copy() {
return new DocumentMetadata(this); // delegates to copy constructor
}
public String getAuthor() { return author; }
public LocalDate getCreatedDate(){ return createdDate; }
public String getVersion() { return version; }
public void setVersion(String v) { this.version = v; }
public void setAuthor(String a) { this.author = a; }
@Override public String toString() {
return String.format("Metadata{author='%s', date=%s, version='%s'}",
author, createdDate, version);
}
}

View File

@@ -0,0 +1,21 @@
package prototype;
import java.util.HashMap;
import java.util.Map;
public class DocumentRegistry {
private final Map<String, Document> prototypes = new HashMap<>();
// Register a prototype under a name
public void register(String name, Document prototype) {
prototypes.put(name, prototype);
}
// Return a fresh copy of the named prototype
public Document get(String name) {
Document prototype = prototypes.get(name);
if (prototype == null) throw new IllegalArgumentException("No prototype: " + name);
return prototype.copy(); // always return a copy, never the prototype itself
}
}

View File

@@ -1,33 +1,47 @@
package prototype;
import java.util.ArrayList;
import java.time.LocalDate;
import java.util.List;
public class Main {
public static void main(String[] args) {
System.out.println("=== Prototype Pattern Demo ===\n");
List<Shape> originals = new ArrayList<>();
originals.add(new Circle(10, "red"));
originals.add(new Rectangle(20, 30, "blue"));
// Create the original document (imagine this was loaded from a database)
DocumentMetadata metadata = new DocumentMetadata("Alice", LocalDate.of(2025, 1, 15), "1.0");
Document original = new Document(
"Q1 Report",
"Revenue increased by 12%...",
List.of("finance", "quarterly"),
metadata
);
// Clone each shape - no need to know the concrete type
List<Shape> copies = new ArrayList<>();
for (Shape shape : originals) {
copies.add(shape.clone());
}
System.out.println("Original: " + original);
// Mutate copies - originals are unaffected
copies.get(0).setColor("green");
copies.get(0).move(5, 5);
copies.get(1).setColor("yellow");
// Clone the document and apply only the differences
Document draft = original.copy();
draft.setTitle("Q1 Report — DRAFT");
draft.addTag("draft");
draft.getMetadata().setAuthor("Bob"); // only the copy's metadata changes
draft.getMetadata().setVersion("1.0-draft");
System.out.println("--- Originals ---");
originals.forEach(System.out::println);
System.out.println("Draft: " + draft);
System.out.println("Original: " + original); // unchanged — deep copy worked
System.out.println("\n--- Clones (mutated) ---");
copies.forEach(System.out::println);
// Verify independence
System.out.println("\nSame object? " + (original == draft)); // false
System.out.println("Same tags? " + (original.getTags() == draft.getTags())); // false
System.out.println("Same meta? " + (original.getMetadata() == draft.getMetadata())); // false
System.out.println("\nSame instance? " + (originals.get(0) == copies.get(0)));
// Registry demo: register a named template once, then hand out independent copies
DocumentRegistry registry = new DocumentRegistry();
registry.register("q-report-template", new Document(
"Quarterly Report Template", "## Executive Summary\n...",
List.of("quarterly", "finance"), metadata));
Document myReport = registry.get("q-report-template"); // a fresh, independent copy
myReport.setTitle("Q2 2025 Report");
myReport.addTag("q2");
System.out.println("\nFrom registry: " + myReport);
}
}

View File

@@ -1,26 +0,0 @@
package prototype;
public class Rectangle extends Shape {
private int width;
private int height;
public Rectangle(int width, int height, String color) {
this.width = width;
this.height = height;
this.color = color;
}
private Rectangle(Rectangle source) {
super(source);
this.width = source.width;
this.height = source.height;
}
@Override
public Rectangle clone() { return new Rectangle(this); }
@Override
public String toString() {
return "Rectangle{color=" + color + ", size=" + width + "x" + height + ", pos=(" + x + "," + y + ")}";
}
}

View File

@@ -1,22 +0,0 @@
package prototype;
/** Prototype - every shape can clone itself */
public abstract class Shape {
protected String color;
protected int x;
protected int y;
protected Shape(Shape source) {
this.color = source.color;
this.x = source.x;
this.y = source.y;
}
protected Shape() {}
public abstract Shape clone();
public void setColor(String color) { this.color = color; }
public String getColor() { return color; }
public void move(int x, int y) { this.x = x; this.y = y; }
}

View File

@@ -0,0 +1,19 @@
package singleton;
public class AppConfig {
private AppConfig() {
System.out.println("Reading configuration from file...");
}
private static class Holder {
static final AppConfig INSTANCE = new AppConfig();
}
public static AppConfig getInstance() {
return Holder.INSTANCE;
}
public String getProperty(String key) {
return System.getProperty(key, "(not set)");
}
}

View File

@@ -1,34 +0,0 @@
package singleton;
/**
* Thread-safe Singleton using double-checked locking.
*/
public class DatabaseConnection {
private static volatile DatabaseConnection instance;
private final String url;
private int queryCount = 0;
private DatabaseConnection() {
this.url = "jdbc:mysql://localhost:3306/myapp";
System.out.println("Establishing connection to " + url);
}
public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
public String executeQuery(String sql) {
queryCount++;
return "Result #" + queryCount + " for: " + sql;
}
public int getQueryCount() { return queryCount; }
public String getUrl() { return url; }
}

View File

@@ -1,19 +1,9 @@
package singleton;
public class Main {
public static void main(String[] args) {
System.out.println("=== Singleton Pattern Demo ===\n");
DatabaseConnection c1 = DatabaseConnection.getInstance();
DatabaseConnection c2 = DatabaseConnection.getInstance();
DatabaseConnection c3 = DatabaseConnection.getInstance();
System.out.println("\nAll three references same instance? " + (c1 == c2 && c2 == c3));
System.out.println(c1.executeQuery("SELECT * FROM users"));
System.out.println(c2.executeQuery("SELECT * FROM orders"));
System.out.println(c3.executeQuery("SELECT COUNT(*) FROM products"));
System.out.println("\nTotal queries: " + c1.getQueryCount());
AppConfig config1 = AppConfig.getInstance();
AppConfig config2 = AppConfig.getInstance();
System.out.println("Same instance? " + (config1 == config2)); // true
System.out.println("db.url = " + config1.getProperty("db.url"));
}
}

View File

@@ -1,6 +1,6 @@
# GoF Design Patterns in Java
Complete runnable Java 17 implementations of all 23 Gang of Four design patterns.
Complete runnable Java 25 implementations of all 23 Gang of Four design patterns.
Each pattern has a dedicated article at [ankurm.com](https://ankurm.com/gof-design-patterns-java/)
with a UML diagram, step-by-step explanation, and console output.
@@ -38,7 +38,7 @@ design-patterns/
## Prerequisites
- Java 17+ (Eclipse Temurin recommended)
- Java 25+ (Eclipse Temurin recommended)
- No build tool required — plain `javac` / `java`
## Run any pattern
@@ -83,6 +83,81 @@ java -cp out\strategy strategy.Main
| Template Method | Behavioral | https://ankurm.com/template-method-design-pattern-java/ |
| Visitor | Behavioral | https://ankurm.com/visitor-design-pattern-java/ |
## Post ↔ Code Mapping
Each article is split into multiple runnable parts. This section maps every post section to the exact file(s) in this repository, so you can jump straight from "what I just read" to "the file that implements it." Updated pattern-by-pattern as each article is verified against Java 25.
### Factory Method (`01-creational/factory-method/`)
| Post Section | File(s) |
|---|---|
| The Problem: Object Creation Leaks Into Business Logic | `NotificationService.java` (anti-pattern shown for contrast — not called by `Main`) |
| Part 1 — The Notification Interface | `Notification.java` |
| Part 2 — The Concrete Notification Classes | `EmailNotification.java`, `SmsNotification.java`, `PushNotification.java` |
| Part 3 — The NotificationSender Class | `NotificationSender.java` |
| Part 4 — The Concrete Sender Subclasses | `EmailSender.java`, `SmsSender.java`, `PushSender.java` |
| Part 5 — Running It: Client Code and Output | `Main.java` |
| Adding a New Channel (Slack) | `SlackNotification.java`, `SlackSender.java` |
Run it:
```bash
javac 01-creational/factory-method/*.java -d out/factory-method
java -cp out/factory-method factorymethod.Main
```
### Abstract Factory (`01-creational/abstract-factory/`)
| Post Section | File(s) |
|---|---|
| Part 1 — The Abstract Products: Button and TextField | `Button.java`, `TextField.java` |
| Part 2 — The Concrete Products (Light family) | `LightButton.java`, `LightTextField.java` |
| Part 2 — The Concrete Products (Dark family) | `DarkButton.java`, `DarkTextField.java` |
| Part 3 — The Abstract Factory: UIFactory Interface | `UIFactory.java` |
| Part 4 — The Concrete Factories | `LightThemeFactory.java`, `DarkThemeFactory.java` |
| Part 5 — The Client: Application | `Application.java`, `Main.java` |
Run it:
```bash
javac 01-creational/abstract-factory/*.java -d out/abstract-factory
java -cp out/abstract-factory abstractfactory.Main
# or, for the dark theme family:
java -Dapp.theme=dark -cp out/abstract-factory abstractfactory.Main
```
### Builder (`01-creational/builder/`)
| Post Section | File(s) |
|---|---|
| Part 1 — The Product: HttpRequest (Immutable) | `HttpRequest.java` |
| Part 2 — The Director: Encapsulating Common Configurations | `HttpClientConfig.java` |
| Part 3 — Using the Builder: Client Code | `Main.java` |
Note: the Lombok `@Builder` example and the JDK standard-library snippets (`HttpRequest.newBuilder()`, `StringBuilder`, `ProcessBuilder`) are illustrative only — they are not part of this repository's runnable example.
Run it:
```bash
javac 01-creational/builder/*.java -d out/builder
java -cp out/builder builder.Main
```
### Prototype (`01-creational/prototype/`)
| Post Section | File(s) |
|---|---|
| Part 1 — The Prototype Interface: Copyable | `Copyable.java` |
| Part 2 — The Nested Object: DocumentMetadata | `DocumentMetadata.java` |
| Part 3 — The Concrete Prototype: Document | `Document.java` |
| Part 4 — The Prototype Registry: DocumentRegistry | `DocumentRegistry.java` |
| Part 5 — Using the Prototype: Client Code | `Main.java` |
Note: the `ShallowVsDeepDemo` snippet (shallow vs. deep copy) and the `Cloneable`-based example are illustrative only — they are not part of this repository's runnable example.
Run it:
```bash
javac 01-creational/prototype/*.java -d out/prototype
java -cp out/prototype prototype.Main
```
## Reference
- *Design Patterns: Elements of Reusable Object-Oriented Software* — Gamma, Helm, Johnson, Vlissides