Sync Factory Method, Abstract Factory, Builder, Prototype to Java 25; fix post/code mismatches
This commit is contained in:
@@ -1,21 +1,30 @@
|
|||||||
package abstractfactory;
|
package abstractfactory;
|
||||||
|
|
||||||
/** Client - uses the factory without knowing concrete classes */
|
|
||||||
public class Application {
|
public class Application {
|
||||||
private final Button button;
|
|
||||||
private final Checkbox checkbox;
|
|
||||||
|
|
||||||
public Application(GUIFactory factory) {
|
// The client holds references to the Abstract Factory and Abstract Products.
|
||||||
button = factory.createButton();
|
// It never imports LightButton, DarkButton, or any concrete class.
|
||||||
checkbox = factory.createCheckbox();
|
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() {
|
public void render() {
|
||||||
button.render();
|
System.out.println("Rendering login screen:");
|
||||||
checkbox.render();
|
usernameField.render();
|
||||||
|
submitButton.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void simulateClick() {
|
public void simulateSubmit() {
|
||||||
button.onClick();
|
submitButton.onClick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package abstractfactory;
|
package abstractfactory;
|
||||||
|
|
||||||
|
// Every button, regardless of theme, must be able to render and handle clicks
|
||||||
public interface Button {
|
public interface Button {
|
||||||
void render();
|
void render(); // draw on screen
|
||||||
void onClick();
|
void onClick(); // respond to a click event
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package abstractfactory;
|
|
||||||
|
|
||||||
public interface Checkbox {
|
|
||||||
void render();
|
|
||||||
}
|
|
||||||
11
01-creational/abstract-factory/DarkButton.java
Normal file
11
01-creational/abstract-factory/DarkButton.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
10
01-creational/abstract-factory/DarkTextField.java
Normal file
10
01-creational/abstract-factory/DarkTextField.java
Normal 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; }
|
||||||
|
}
|
||||||
14
01-creational/abstract-factory/DarkThemeFactory.java
Normal file
14
01-creational/abstract-factory/DarkThemeFactory.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package abstractfactory;
|
|
||||||
|
|
||||||
/** Abstract Factory - creates families of related UI components */
|
|
||||||
public interface GUIFactory {
|
|
||||||
Button createButton();
|
|
||||||
Checkbox createCheckbox();
|
|
||||||
}
|
|
||||||
11
01-creational/abstract-factory/LightButton.java
Normal file
11
01-creational/abstract-factory/LightButton.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
10
01-creational/abstract-factory/LightTextField.java
Normal file
10
01-creational/abstract-factory/LightTextField.java
Normal 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; }
|
||||||
|
}
|
||||||
14
01-creational/abstract-factory/LightThemeFactory.java
Normal file
14
01-creational/abstract-factory/LightThemeFactory.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"); }
|
|
||||||
}
|
|
||||||
@@ -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"); }
|
|
||||||
}
|
|
||||||
@@ -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(); }
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,16 @@
|
|||||||
package abstractfactory;
|
package abstractfactory;
|
||||||
|
|
||||||
|
// Entry point: the ONLY place that knows the theme
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println("=== Abstract Factory Pattern Demo ===\n");
|
|
||||||
|
|
||||||
String os = System.getProperty("os.name", "Windows").toLowerCase();
|
// Production: read theme from config or OS preference
|
||||||
GUIFactory factory = os.contains("mac") ? new MacFactory() : new WindowsFactory();
|
String theme = System.getProperty("app.theme", "light");
|
||||||
|
UIFactory factory = "dark".equals(theme) ? new DarkThemeFactory() : new LightThemeFactory();
|
||||||
System.out.println("Detected OS family: " + (os.contains("mac") ? "Mac" : "Windows"));
|
|
||||||
System.out.println();
|
|
||||||
|
|
||||||
Application app = new Application(factory);
|
Application app = new Application(factory);
|
||||||
|
app.buildUI();
|
||||||
app.render();
|
app.render();
|
||||||
app.simulateClick();
|
app.simulateSubmit();
|
||||||
|
|
||||||
System.out.println("\n--- Forcing Mac UI ---");
|
|
||||||
Application macApp = new Application(new MacFactory());
|
|
||||||
macApp.render();
|
|
||||||
macApp.simulateClick();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
01-creational/abstract-factory/TextField.java
Normal file
7
01-creational/abstract-factory/TextField.java
Normal 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();
|
||||||
|
}
|
||||||
13
01-creational/abstract-factory/UIFactory.java
Normal file
13
01-creational/abstract-factory/UIFactory.java
Normal 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.
|
||||||
|
}
|
||||||
@@ -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"); }
|
|
||||||
}
|
|
||||||
@@ -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"); }
|
|
||||||
}
|
|
||||||
@@ -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(); }
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
27
01-creational/builder/HttpClientConfig.java
Normal file
27
01-creational/builder/HttpClientConfig.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
76
01-creational/builder/HttpRequest.java
Normal file
76
01-creational/builder/HttpRequest.java
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,42 @@
|
|||||||
package builder;
|
package builder;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
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("Simple GET: " + ping);
|
||||||
System.out.println("Starter home : " + starter);
|
|
||||||
|
|
||||||
House luxury = director.buildLuxury(new House.Builder());
|
// Full POST with authentication and retry
|
||||||
System.out.println("Luxury home : " + luxury);
|
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
|
System.out.println("POST: " + post);
|
||||||
House custom = new House.Builder()
|
|
||||||
.rooms(4).floors(2).garden(true).roofType("mansard").build();
|
// build() throws if url is missing — validation at construction time
|
||||||
System.out.println("Custom home : " + custom);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package factorymethod;
|
package factorymethod;
|
||||||
|
|
||||||
public class EmailNotification implements Notification {
|
public class EmailNotification implements Notification {
|
||||||
private final String email;
|
|
||||||
public EmailNotification(String email) { this.email = email; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(String message) {
|
public void send(String recipient, String message) {
|
||||||
System.out.println("Email -> " + email + ": " + 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"; }
|
||||||
}
|
}
|
||||||
|
|||||||
8
01-creational/factory-method/EmailSender.java
Normal file
8
01-creational/factory-method/EmailSender.java
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package factorymethod;
|
||||||
|
|
||||||
|
public class EmailSender extends NotificationSender {
|
||||||
|
@Override
|
||||||
|
protected Notification createNotification() {
|
||||||
|
return new EmailNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,23 @@
|
|||||||
package factorymethod;
|
package factorymethod;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println("=== Factory Method Pattern Demo ===\n");
|
|
||||||
|
|
||||||
NotificationService[] services = {
|
// The client only knows about NotificationSender (the Creator)
|
||||||
new EmailService("alice@example.com"),
|
NotificationSender sender;
|
||||||
new SmsService("+1-555-0123"),
|
|
||||||
new PushService("device-token-abc123")
|
|
||||||
};
|
|
||||||
|
|
||||||
for (NotificationService service : services) {
|
sender = new EmailSender();
|
||||||
service.notifyUser("Your order #1042 has been shipped!");
|
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%");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
package factorymethod;
|
package factorymethod;
|
||||||
|
|
||||||
public interface Notification {
|
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();
|
||||||
}
|
}
|
||||||
|
|||||||
31
01-creational/factory-method/NotificationSender.java
Normal file
31
01-creational/factory-method/NotificationSender.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,24 @@
|
|||||||
package factorymethod;
|
package factorymethod;
|
||||||
|
|
||||||
/** Creator - declares the factory method */
|
public class NotificationService {
|
||||||
public abstract class NotificationService {
|
|
||||||
protected abstract Notification createNotification();
|
|
||||||
|
|
||||||
public void notifyUser(String message) {
|
public void send(String type, String recipient, String message) {
|
||||||
Notification n = createNotification();
|
|
||||||
n.send(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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package factorymethod;
|
package factorymethod;
|
||||||
|
|
||||||
public class PushNotification implements Notification {
|
public class PushNotification implements Notification {
|
||||||
private final String deviceToken;
|
|
||||||
public PushNotification(String deviceToken) { this.deviceToken = deviceToken; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(String message) {
|
public void send(String recipient, String message) {
|
||||||
System.out.println("Push -> " + deviceToken + ": " + message);
|
// In production: use Firebase FCM
|
||||||
|
System.out.printf(" [PUSH] To: %s | Alert: %s%n", recipient, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() { return "PUSH"; }
|
||||||
}
|
}
|
||||||
|
|||||||
8
01-creational/factory-method/PushSender.java
Normal file
8
01-creational/factory-method/PushSender.java
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package factorymethod;
|
||||||
|
|
||||||
|
public class PushSender extends NotificationSender {
|
||||||
|
@Override
|
||||||
|
protected Notification createNotification() {
|
||||||
|
return new PushNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
01-creational/factory-method/SlackNotification.java
Normal file
11
01-creational/factory-method/SlackNotification.java
Normal 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"; }
|
||||||
|
}
|
||||||
9
01-creational/factory-method/SlackSender.java
Normal file
9
01-creational/factory-method/SlackSender.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package factorymethod;
|
package factorymethod;
|
||||||
|
|
||||||
public class SmsNotification implements Notification {
|
public class SmsNotification implements Notification {
|
||||||
private final String phone;
|
|
||||||
public SmsNotification(String phone) { this.phone = phone; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(String message) {
|
public void send(String recipient, String message) {
|
||||||
System.out.println("SMS -> " + phone + ": " + message);
|
// In production: use Twilio SDK
|
||||||
|
System.out.printf(" [SMS] To: %s | Text: %s%n", recipient, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() { return "SMS"; }
|
||||||
}
|
}
|
||||||
|
|||||||
8
01-creational/factory-method/SmsSender.java
Normal file
8
01-creational/factory-method/SmsSender.java
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package factorymethod;
|
||||||
|
|
||||||
|
public class SmsSender extends NotificationSender {
|
||||||
|
@Override
|
||||||
|
protected Notification createNotification() {
|
||||||
|
return new SmsNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 + ")}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
8
01-creational/prototype/Copyable.java
Normal file
8
01-creational/prototype/Copyable.java
Normal 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();
|
||||||
|
}
|
||||||
46
01-creational/prototype/Document.java
Normal file
46
01-creational/prototype/Document.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
01-creational/prototype/DocumentMetadata.java
Normal file
39
01-creational/prototype/DocumentMetadata.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
01-creational/prototype/DocumentRegistry.java
Normal file
21
01-creational/prototype/DocumentRegistry.java
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +1,47 @@
|
|||||||
package prototype;
|
package prototype;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println("=== Prototype Pattern Demo ===\n");
|
|
||||||
|
|
||||||
List<Shape> originals = new ArrayList<>();
|
// Create the original document (imagine this was loaded from a database)
|
||||||
originals.add(new Circle(10, "red"));
|
DocumentMetadata metadata = new DocumentMetadata("Alice", LocalDate.of(2025, 1, 15), "1.0");
|
||||||
originals.add(new Rectangle(20, 30, "blue"));
|
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
|
System.out.println("Original: " + original);
|
||||||
List<Shape> copies = new ArrayList<>();
|
|
||||||
for (Shape shape : originals) {
|
|
||||||
copies.add(shape.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mutate copies - originals are unaffected
|
// Clone the document and apply only the differences
|
||||||
copies.get(0).setColor("green");
|
Document draft = original.copy();
|
||||||
copies.get(0).move(5, 5);
|
draft.setTitle("Q1 Report — DRAFT");
|
||||||
copies.get(1).setColor("yellow");
|
draft.addTag("draft");
|
||||||
|
draft.getMetadata().setAuthor("Bob"); // only the copy's metadata changes
|
||||||
|
draft.getMetadata().setVersion("1.0-draft");
|
||||||
|
|
||||||
System.out.println("--- Originals ---");
|
System.out.println("Draft: " + draft);
|
||||||
originals.forEach(System.out::println);
|
System.out.println("Original: " + original); // unchanged — deep copy worked
|
||||||
|
|
||||||
System.out.println("\n--- Clones (mutated) ---");
|
// Verify independence
|
||||||
copies.forEach(System.out::println);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 + ")}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
19
01-creational/singleton/AppConfig.java
Normal file
19
01-creational/singleton/AppConfig.java
Normal 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)");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,9 @@
|
|||||||
package singleton;
|
package singleton;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println("=== Singleton Pattern Demo ===\n");
|
AppConfig config1 = AppConfig.getInstance();
|
||||||
|
AppConfig config2 = AppConfig.getInstance();
|
||||||
DatabaseConnection c1 = DatabaseConnection.getInstance();
|
System.out.println("Same instance? " + (config1 == config2)); // true
|
||||||
DatabaseConnection c2 = DatabaseConnection.getInstance();
|
System.out.println("db.url = " + config1.getProperty("db.url"));
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
README.md
79
README.md
@@ -1,6 +1,6 @@
|
|||||||
# GoF Design Patterns in Java
|
# 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/)
|
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.
|
with a UML diagram, step-by-step explanation, and console output.
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ design-patterns/
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Java 17+ (Eclipse Temurin recommended)
|
- Java 25+ (Eclipse Temurin recommended)
|
||||||
- No build tool required — plain `javac` / `java`
|
- No build tool required — plain `javac` / `java`
|
||||||
|
|
||||||
## Run any pattern
|
## Run any pattern
|
||||||
@@ -83,6 +83,81 @@ java -cp out\strategy strategy.Main
|
|||||||
| Template Method | Behavioral | https://ankurm.com/template-method-design-pattern-java/ |
|
| Template Method | Behavioral | https://ankurm.com/template-method-design-pattern-java/ |
|
||||||
| Visitor | Behavioral | https://ankurm.com/visitor-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
|
## Reference
|
||||||
|
|
||||||
- *Design Patterns: Elements of Reusable Object-Oriented Software* — Gamma, Helm, Johnson, Vlissides
|
- *Design Patterns: Elements of Reusable Object-Oriented Software* — Gamma, Helm, Johnson, Vlissides
|
||||||
|
|||||||
Reference in New Issue
Block a user