Add all 23 GoF design pattern implementations (2026-06-13)
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
out/
|
||||
*.class
|
||||
screenshots/*.png
|
||||
*.b64.txt
|
||||
38
02-structural/adapter/Main.java
Normal file
38
02-structural/adapter/Main.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package adapter;
|
||||
|
||||
/**
|
||||
* Adapter Design Pattern — Runnable Demo
|
||||
*
|
||||
* Demonstrates wrapping an incompatible StripeClient behind
|
||||
* the PaymentGateway interface your application expects.
|
||||
*
|
||||
* Run: javac adapter/*.java && java adapter.Main
|
||||
* Article: https://ankurm.com/adapter-design-pattern-java/
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Adapter Design Pattern Demo ===\n");
|
||||
|
||||
// --- Object Adapter: Stripe ---
|
||||
System.out.println("-- Using Stripe via Adapter --");
|
||||
StripeClient stripeClient = new StripeClient("sk_test_4eC39HqLyjWDarjtT1zdp7dc");
|
||||
PaymentGateway stripeGateway = new StripePaymentAdapter(stripeClient);
|
||||
|
||||
OrderService orderService = new OrderService(stripeGateway);
|
||||
orderService.processOrder("ORD-001", "cus_abc123", 99.99);
|
||||
|
||||
System.out.println("\n-- Testing refund --");
|
||||
boolean refunded = stripeGateway.refund("ch_ORD-001", 99.99);
|
||||
System.out.println("Refund issued: " + refunded);
|
||||
|
||||
// --- JDK Example: InputStreamReader as Adapter ---
|
||||
System.out.println("\n-- JDK Adapter: InputStreamReader --");
|
||||
// InputStreamReader adapts InputStream (byte-based) to Reader (char-based)
|
||||
// The client (BufferedReader) only knows about Reader, not InputStream
|
||||
System.out.println("InputStreamReader wraps System.in (InputStream) as a Reader.");
|
||||
System.out.println("Your code reads chars; the adapter handles byte-to-char conversion.");
|
||||
|
||||
System.out.println("\n=== Demo complete ===");
|
||||
}
|
||||
}
|
||||
31
02-structural/adapter/OrderService.java
Normal file
31
02-structural/adapter/OrderService.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package adapter;
|
||||
|
||||
/**
|
||||
* The Client — uses only the PaymentGateway interface.
|
||||
* It has no idea whether it's talking to Stripe, PayPal, or Braintree.
|
||||
* This is the point: the client is completely isolated from the vendor.
|
||||
*/
|
||||
public class OrderService {
|
||||
|
||||
private final PaymentGateway gateway;
|
||||
|
||||
// Receives a PaymentGateway — could be Stripe, PayPal, anything
|
||||
public OrderService(PaymentGateway gateway) {
|
||||
this.gateway = gateway;
|
||||
}
|
||||
|
||||
public void processOrder(String orderId, String customerId, double total) {
|
||||
System.out.printf("%nProcessing order %s for customer %s, total: $%.2f%n",
|
||||
orderId, customerId, total);
|
||||
|
||||
boolean charged = gateway.charge(customerId, total, "USD");
|
||||
|
||||
if (charged) {
|
||||
System.out.println("Payment accepted. Order confirmed.");
|
||||
String status = gateway.getStatus("ch_" + orderId);
|
||||
System.out.println("Transaction status: " + status);
|
||||
} else {
|
||||
System.out.println("Payment failed. Order rejected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
12
02-structural/adapter/PaymentGateway.java
Normal file
12
02-structural/adapter/PaymentGateway.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package adapter;
|
||||
|
||||
/**
|
||||
* The Target interface — what YOUR application's code expects.
|
||||
* Every payment gateway in your system must implement this.
|
||||
* Written to handle modern async-style payment flows.
|
||||
*/
|
||||
public interface PaymentGateway {
|
||||
boolean charge(String customerId, double amount, String currency);
|
||||
boolean refund(String transactionId, double amount);
|
||||
String getStatus(String transactionId);
|
||||
}
|
||||
33
02-structural/adapter/README.md
Normal file
33
02-structural/adapter/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Adapter Design Pattern — Java Example
|
||||
|
||||
**Pattern:** Structural → Adapter
|
||||
**Article:** https://ankurm.com/adapter-design-pattern-java/
|
||||
|
||||
## What this example shows
|
||||
|
||||
Wraps a third-party `StripeClient` (with its own API) behind a `PaymentGateway` interface that your application code expects. The `OrderService` client never touches `StripeClient` directly — it only sees `PaymentGateway`. Swapping payment providers requires changing one line.
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
# From this folder:
|
||||
javac adapter/*.java
|
||||
java adapter.Main
|
||||
```
|
||||
|
||||
Requires Java 11+.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `PaymentGateway.java` | Target interface (what your app expects) |
|
||||
| `StripeClient.java` | Adaptee (third-party SDK you can't modify) |
|
||||
| `StripePaymentAdapter.java` | Adapter (bridges the two) |
|
||||
| `OrderService.java` | Client (only uses PaymentGateway) |
|
||||
| `Main.java` | Demo entry point |
|
||||
|
||||
## See Also
|
||||
|
||||
- Full article: https://ankurm.com/adapter-design-pattern-java/
|
||||
- All design patterns: https://ankurm.com/design-patterns-java/
|
||||
51
02-structural/adapter/StripeClient.java
Normal file
51
02-structural/adapter/StripeClient.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package adapter;
|
||||
|
||||
/**
|
||||
* The Adaptee — a third-party payment SDK with a completely different interface.
|
||||
* Imagine this is Stripe's actual SDK: you cannot modify this class,
|
||||
* and it doesn't implement PaymentGateway.
|
||||
*
|
||||
* In real projects, this would be a JAR you depend on.
|
||||
*/
|
||||
public class StripeClient {
|
||||
|
||||
private final String apiKey;
|
||||
|
||||
public StripeClient(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
System.out.println("[Stripe] Initialized with key: " + apiKey.substring(0, 8) + "...");
|
||||
}
|
||||
|
||||
// Stripe uses cents, not decimal amounts
|
||||
public StripeChargeResult createCharge(String customerId, long amountInCents, String currency) {
|
||||
System.out.printf("[Stripe] Charging customer=%s, amount=%d cents, currency=%s%n",
|
||||
customerId, amountInCents, currency);
|
||||
// Simulate success
|
||||
return new StripeChargeResult("ch_" + System.currentTimeMillis(), true, null);
|
||||
}
|
||||
|
||||
// Stripe's refund method takes a charge ID and uses different naming
|
||||
public boolean issueRefund(String chargeId, long amountInCents) {
|
||||
System.out.printf("[Stripe] Refunding charge=%s, amount=%d cents%n", chargeId, amountInCents);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stripe uses 'retrieve' not 'getStatus', and returns an object
|
||||
public StripeChargeResult retrieveCharge(String chargeId) {
|
||||
System.out.printf("[Stripe] Retrieving charge=%s%n", chargeId);
|
||||
return new StripeChargeResult(chargeId, true, "succeeded");
|
||||
}
|
||||
|
||||
// Stripe-specific result object — nothing in common with your domain
|
||||
public static class StripeChargeResult {
|
||||
public final String chargeId;
|
||||
public final boolean success;
|
||||
public final String status;
|
||||
|
||||
public StripeChargeResult(String chargeId, boolean success, String status) {
|
||||
this.chargeId = chargeId;
|
||||
this.success = success;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
02-structural/adapter/StripePaymentAdapter.java
Normal file
44
02-structural/adapter/StripePaymentAdapter.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package adapter;
|
||||
|
||||
/**
|
||||
* The Adapter — bridges StripeClient (Adaptee) to PaymentGateway (Target).
|
||||
*
|
||||
* This is the Object Adapter variant: it holds a StripeClient instance
|
||||
* via composition (not inheritance), so it can adapt any StripeClient
|
||||
* including subclasses.
|
||||
*
|
||||
* The key responsibility: translate YOUR interface's methods into
|
||||
* calls that Stripe understands — data conversion included.
|
||||
*/
|
||||
public class StripePaymentAdapter implements PaymentGateway {
|
||||
|
||||
private final StripeClient stripe;
|
||||
|
||||
public StripePaymentAdapter(StripeClient stripe) {
|
||||
this.stripe = stripe;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean charge(String customerId, double amount, String currency) {
|
||||
// Translation: your code uses decimal dollars; Stripe wants integer cents
|
||||
long amountInCents = Math.round(amount * 100);
|
||||
StripeClient.StripeChargeResult result =
|
||||
stripe.createCharge(customerId, amountInCents, currency.toLowerCase());
|
||||
return result.success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean refund(String transactionId, double amount) {
|
||||
// Translation: your "transactionId" is Stripe's "chargeId"
|
||||
long amountInCents = Math.round(amount * 100);
|
||||
return stripe.issueRefund(transactionId, amountInCents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus(String transactionId) {
|
||||
// Translation: map Stripe's object to your simple status string
|
||||
StripeClient.StripeChargeResult result = stripe.retrieveCharge(transactionId);
|
||||
if (!result.success) return "FAILED";
|
||||
return result.status != null ? result.status.toUpperCase() : "UNKNOWN";
|
||||
}
|
||||
}
|
||||
25
02-structural/bridge/AdvancedRemote.java
Normal file
25
02-structural/bridge/AdvancedRemote.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package bridge;
|
||||
|
||||
/**
|
||||
* Refined Abstraction — extends RemoteControl with extra features.
|
||||
* This is how you vary the "abstraction" side independently of the
|
||||
* "implementation" side. Both TV and Radio work with this remote,
|
||||
* even though they know nothing about it.
|
||||
*/
|
||||
public class AdvancedRemote extends RemoteControl {
|
||||
|
||||
public AdvancedRemote(Device device) {
|
||||
super(device);
|
||||
}
|
||||
|
||||
// Extra feature not in the basic remote
|
||||
public void mute() {
|
||||
System.out.println(" Muting " + device.getName());
|
||||
device.setVolume(0);
|
||||
}
|
||||
|
||||
public void jumpToChannel(int channel) {
|
||||
System.out.println(" Jumping to channel " + channel);
|
||||
device.setChannel(channel);
|
||||
}
|
||||
}
|
||||
17
02-structural/bridge/Device.java
Normal file
17
02-structural/bridge/Device.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package bridge;
|
||||
|
||||
/**
|
||||
* Implementor interface — the "implementation" side of the bridge.
|
||||
* This is what the Abstraction delegates its real work to.
|
||||
* TV, Radio, SmartSpeaker etc. all implement this.
|
||||
*/
|
||||
public interface Device {
|
||||
boolean isEnabled();
|
||||
void enable();
|
||||
void disable();
|
||||
int getVolume();
|
||||
void setVolume(int percent);
|
||||
int getChannel();
|
||||
void setChannel(int channel);
|
||||
String getName();
|
||||
}
|
||||
47
02-structural/bridge/Main.java
Normal file
47
02-structural/bridge/Main.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package bridge;
|
||||
|
||||
/**
|
||||
* Bridge Design Pattern — Runnable Demo
|
||||
*
|
||||
* Shows how remotes (abstraction) and devices (implementation)
|
||||
* can vary independently. 4 combinations from 2+2 classes,
|
||||
* not 4 hard-coded classes.
|
||||
*
|
||||
* Run: javac bridge/*.java && java bridge.Main
|
||||
* Article: https://ankurm.com/bridge-design-pattern-java/
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Bridge Design Pattern Demo ===\n");
|
||||
|
||||
// Combination 1: Basic Remote + TV
|
||||
System.out.println("-- Basic Remote controlling TV --");
|
||||
RemoteControl remote1 = new RemoteControl(new TV());
|
||||
remote1.togglePower();
|
||||
remote1.volumeUp();
|
||||
remote1.channelUp();
|
||||
|
||||
System.out.println();
|
||||
|
||||
// Combination 2: Advanced Remote + Radio
|
||||
System.out.println("-- Advanced Remote controlling Radio --");
|
||||
AdvancedRemote remote2 = new AdvancedRemote(new Radio());
|
||||
remote2.togglePower();
|
||||
remote2.volumeUp();
|
||||
remote2.mute();
|
||||
remote2.jumpToChannel(91);
|
||||
|
||||
System.out.println();
|
||||
|
||||
// Combination 3: Advanced Remote + TV (no new classes needed)
|
||||
System.out.println("-- Advanced Remote controlling TV --");
|
||||
AdvancedRemote remote3 = new AdvancedRemote(new TV());
|
||||
remote3.togglePower();
|
||||
remote3.jumpToChannel(5);
|
||||
remote3.mute();
|
||||
|
||||
System.out.println("\n=== Demo complete ===");
|
||||
System.out.println("3 different remote+device combinations, 0 new classes needed.");
|
||||
}
|
||||
}
|
||||
30
02-structural/bridge/README.md
Normal file
30
02-structural/bridge/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Bridge Design Pattern — Java Example
|
||||
|
||||
**Pattern:** Structural → Bridge
|
||||
**Article:** https://ankurm.com/bridge-design-pattern-java/
|
||||
|
||||
## What this example shows
|
||||
|
||||
Decouples remote controls (Abstraction) from devices (Implementation). A basic remote and an advanced remote each work with any device (TV, Radio) without creating N×M subclasses.
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
javac bridge/*.java
|
||||
java bridge.Main
|
||||
```
|
||||
|
||||
Requires Java 11+.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `Device.java` | Implementor interface |
|
||||
| `TV.java` / `Radio.java` | Concrete Implementors |
|
||||
| `RemoteControl.java` | Abstraction (holds Device bridge) |
|
||||
| `AdvancedRemote.java` | Refined Abstraction |
|
||||
| `Main.java` | Demo entry point |
|
||||
|
||||
Article: https://ankurm.com/bridge-design-pattern-java/
|
||||
All patterns: https://ankurm.com/design-patterns-java/
|
||||
29
02-structural/bridge/Radio.java
Normal file
29
02-structural/bridge/Radio.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package bridge;
|
||||
|
||||
/**
|
||||
* Concrete Implementor — a radio.
|
||||
* Same Device interface, completely different internal behaviour.
|
||||
*/
|
||||
public class Radio implements Device {
|
||||
|
||||
private boolean on = false;
|
||||
private int volume = 20;
|
||||
private int channel = 1; // FM frequency index simplified
|
||||
|
||||
@Override public boolean isEnabled() { return on; }
|
||||
@Override public void enable() { on = true; System.out.println(" [Radio] Powered ON"); }
|
||||
@Override public void disable() { on = false; System.out.println(" [Radio] Powered OFF"); }
|
||||
|
||||
@Override
|
||||
public int getVolume() { return volume; }
|
||||
|
||||
@Override
|
||||
public void setVolume(int percent) {
|
||||
this.volume = Math.max(0, Math.min(100, percent));
|
||||
System.out.println(" [Radio] Volume set to " + this.volume);
|
||||
}
|
||||
|
||||
@Override public int getChannel() { return channel; }
|
||||
@Override public void setChannel(int ch) { this.channel = ch; System.out.println(" [Radio] Frequency -> " + ch); }
|
||||
@Override public String getName() { return "JBL Radio"; }
|
||||
}
|
||||
33
02-structural/bridge/RemoteControl.java
Normal file
33
02-structural/bridge/RemoteControl.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package bridge;
|
||||
|
||||
/**
|
||||
* Abstraction — the remote control. It holds a reference to a Device
|
||||
* (the "bridge") and delegates all real work to it.
|
||||
*
|
||||
* The key: RemoteControl doesn't care whether it talks to a TV or Radio.
|
||||
* It holds a Device and calls Device methods. That's the bridge.
|
||||
*/
|
||||
public class RemoteControl {
|
||||
|
||||
// The bridge — link from Abstraction to Implementation
|
||||
protected Device device;
|
||||
|
||||
public RemoteControl(Device device) {
|
||||
this.device = device;
|
||||
System.out.println("Remote paired with: " + device.getName());
|
||||
}
|
||||
|
||||
public void togglePower() {
|
||||
if (device.isEnabled()) {
|
||||
device.disable();
|
||||
} else {
|
||||
device.enable();
|
||||
}
|
||||
}
|
||||
|
||||
public void volumeUp() { device.setVolume(device.getVolume() + 10); }
|
||||
public void volumeDown() { device.setVolume(device.getVolume() - 10); }
|
||||
|
||||
public void channelUp() { device.setChannel(device.getChannel() + 1); }
|
||||
public void channelDown() { device.setChannel(device.getChannel() - 1); }
|
||||
}
|
||||
29
02-structural/bridge/TV.java
Normal file
29
02-structural/bridge/TV.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package bridge;
|
||||
|
||||
/**
|
||||
* Concrete Implementor — a television.
|
||||
* Contains device-specific logic for a TV.
|
||||
*/
|
||||
public class TV implements Device {
|
||||
|
||||
private boolean on = false;
|
||||
private int volume = 30;
|
||||
private int channel = 1;
|
||||
|
||||
@Override public boolean isEnabled() { return on; }
|
||||
@Override public void enable() { on = true; System.out.println(" [TV] Powered ON"); }
|
||||
@Override public void disable() { on = false; System.out.println(" [TV] Powered OFF"); }
|
||||
|
||||
@Override
|
||||
public int getVolume() { return volume; }
|
||||
|
||||
@Override
|
||||
public void setVolume(int percent) {
|
||||
this.volume = Math.max(0, Math.min(100, percent));
|
||||
System.out.println(" [TV] Volume set to " + this.volume);
|
||||
}
|
||||
|
||||
@Override public int getChannel() { return channel; }
|
||||
@Override public void setChannel(int ch) { this.channel = ch; System.out.println(" [TV] Channel -> " + ch); }
|
||||
@Override public String getName() { return "Samsung TV"; }
|
||||
}
|
||||
53
02-structural/composite/Directory.java
Normal file
53
02-structural/composite/Directory.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package composite;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Composite — a directory that can hold both Files (leaves)
|
||||
* and other Directories (composites).
|
||||
*
|
||||
* getSize() is recursive: it asks each child for its size and sums them.
|
||||
* The caller doesn't care whether a child is a File or Directory —
|
||||
* both implement FileSystemItem and answer getSize().
|
||||
*
|
||||
* This is the power of Composite: uniform treatment of simple and complex.
|
||||
*/
|
||||
public class Directory implements FileSystemItem {
|
||||
|
||||
private final String name;
|
||||
private final List<FileSystemItem> children = new ArrayList<>();
|
||||
|
||||
public Directory(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Directory add(FileSystemItem item) {
|
||||
children.add(item);
|
||||
return this; // fluent API for easy nesting
|
||||
}
|
||||
|
||||
public void remove(FileSystemItem item) {
|
||||
children.remove(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() { return name; }
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
// Recursion: each child knows its own size.
|
||||
// Files return their bytes; directories sum their children.
|
||||
return children.stream()
|
||||
.mapToLong(FileSystemItem::getSize)
|
||||
.sum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String indent) {
|
||||
System.out.printf("%s[DIR] %s/ (%,d bytes total)%n", indent, name, getSize());
|
||||
for (FileSystemItem child : children) {
|
||||
child.print(indent + " "); // recurse with deeper indent
|
||||
}
|
||||
}
|
||||
}
|
||||
27
02-structural/composite/File.java
Normal file
27
02-structural/composite/File.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package composite;
|
||||
|
||||
/**
|
||||
* Leaf — a single file. It has no children.
|
||||
* getSize() returns its own size. print() shows its name.
|
||||
*
|
||||
* Notice: the File has no knowledge of directories or nesting.
|
||||
* It just knows its own name and size.
|
||||
*/
|
||||
public class File implements FileSystemItem {
|
||||
|
||||
private final String name;
|
||||
private final long size;
|
||||
|
||||
public File(String name, long sizeBytes) {
|
||||
this.name = name;
|
||||
this.size = sizeBytes;
|
||||
}
|
||||
|
||||
@Override public String getName() { return name; }
|
||||
@Override public long getSize() { return size; }
|
||||
|
||||
@Override
|
||||
public void print(String indent) {
|
||||
System.out.printf("%s[FILE] %s (%,d bytes)%n", indent, name, size);
|
||||
}
|
||||
}
|
||||
12
02-structural/composite/FileSystemItem.java
Normal file
12
02-structural/composite/FileSystemItem.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package composite;
|
||||
|
||||
/**
|
||||
* Component interface — the common contract for BOTH files (leaves)
|
||||
* and directories (composites). Clients work through this interface
|
||||
* and never need to know which they're dealing with.
|
||||
*/
|
||||
public interface FileSystemItem {
|
||||
String getName();
|
||||
long getSize(); // total size in bytes (recursive for directories)
|
||||
void print(String indent); // display the tree
|
||||
}
|
||||
58
02-structural/composite/Main.java
Normal file
58
02-structural/composite/Main.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package composite;
|
||||
|
||||
/**
|
||||
* Composite Design Pattern — Runnable Demo
|
||||
*
|
||||
* Builds a file system tree with nested directories and files.
|
||||
* Demonstrates that getSize() and print() work uniformly on
|
||||
* leaves (File) and composites (Directory) without any instanceof checks.
|
||||
*
|
||||
* Run: javac composite/*.java && java composite.Main
|
||||
* Article: https://ankurm.com/composite-design-pattern-java/
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Composite Design Pattern Demo ===\n");
|
||||
|
||||
// Build a file system tree
|
||||
Directory root = new Directory("project");
|
||||
|
||||
Directory src = new Directory("src");
|
||||
Directory main = new Directory("main");
|
||||
main.add(new File("App.java", 4_200))
|
||||
.add(new File("Config.java", 1_800))
|
||||
.add(new File("Application.yml", 3_100));
|
||||
|
||||
Directory test = new Directory("test");
|
||||
test.add(new File("AppTest.java", 2_600))
|
||||
.add(new File("ConfigTest.java", 1_200));
|
||||
|
||||
src.add(main).add(test);
|
||||
|
||||
Directory resources = new Directory("resources");
|
||||
resources.add(new File("application.yml", 1_500))
|
||||
.add(new File("logback.xml", 900))
|
||||
.add(new File("banner.txt", 200));
|
||||
|
||||
root.add(src)
|
||||
.add(resources)
|
||||
.add(new File("pom.xml", 8_400))
|
||||
.add(new File("README.md", 2_100));
|
||||
|
||||
// Print entire tree — recursion happens automatically
|
||||
System.out.println("File system tree:");
|
||||
root.print("");
|
||||
|
||||
System.out.printf("%nTotal project size: %,d bytes%n", root.getSize());
|
||||
|
||||
// Client treats File and Directory identically
|
||||
System.out.println("\n-- Treating File and Directory uniformly --");
|
||||
FileSystemItem[] items = { new File("standalone.txt", 500), src };
|
||||
for (FileSystemItem item : items) {
|
||||
System.out.printf("%s -> size: %,d bytes%n", item.getName(), item.getSize());
|
||||
}
|
||||
|
||||
System.out.println("\n=== Demo complete ===");
|
||||
}
|
||||
}
|
||||
53
02-structural/decorator/Main.java
Normal file
53
02-structural/decorator/Main.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package decorator;
|
||||
|
||||
/**
|
||||
* Decorator Design Pattern — Runnable Demo
|
||||
*
|
||||
* Shows how text processors can be stacked like Java IO streams.
|
||||
* Each decorator adds one behaviour; the order of wrapping matters.
|
||||
*
|
||||
* Run: javac decorator/*.java && java decorator.Main
|
||||
* Article: https://ankurm.com/decorator-design-pattern-java/
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Decorator Design Pattern Demo ===\n");
|
||||
|
||||
String input = " hello world, badword is here ";
|
||||
System.out.println("Input: \"" + input + "\"");
|
||||
System.out.println();
|
||||
|
||||
// Stack 1: just trim
|
||||
TextProcessor trimOnly = new TrimDecorator(new PlainTextProcessor());
|
||||
System.out.println("Trim only: \"" + trimOnly.process(input) + "\"");
|
||||
|
||||
// Stack 2: trim, then upper case
|
||||
TextProcessor trimThenUpper =
|
||||
new UpperCaseDecorator(
|
||||
new TrimDecorator(
|
||||
new PlainTextProcessor()));
|
||||
System.out.println("Trim + UpperCase: \"" + trimThenUpper.process(input) + "\"");
|
||||
|
||||
// Stack 3: trim, filter profanity, then upper case
|
||||
TextProcessor full =
|
||||
new UpperCaseDecorator(
|
||||
new ProfanityFilterDecorator(
|
||||
new TrimDecorator(
|
||||
new PlainTextProcessor())));
|
||||
System.out.println("Trim + Filter + Upper: \"" + full.process(input) + "\"");
|
||||
|
||||
// Stack 4: different order — filter then trim (order matters!)
|
||||
TextProcessor filterFirst =
|
||||
new TrimDecorator(
|
||||
new ProfanityFilterDecorator(
|
||||
new PlainTextProcessor()));
|
||||
System.out.println("Filter + Trim: \"" + filterFirst.process(input) + "\"");
|
||||
|
||||
System.out.println();
|
||||
System.out.println("JDK parallel: new BufferedReader(new InputStreamReader(socket.getInputStream()))");
|
||||
System.out.println("Same pattern: each wrapper adds one behaviour, order matters.");
|
||||
|
||||
System.out.println("\n=== Demo complete ===");
|
||||
}
|
||||
}
|
||||
14
02-structural/decorator/PlainTextProcessor.java
Normal file
14
02-structural/decorator/PlainTextProcessor.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package decorator;
|
||||
|
||||
/**
|
||||
* Concrete Component — the base object being decorated.
|
||||
* Does nothing special: just returns the text as-is.
|
||||
* All decorators wrap this (or other decorators on top of it).
|
||||
*/
|
||||
public class PlainTextProcessor implements TextProcessor {
|
||||
|
||||
@Override
|
||||
public String process(String text) {
|
||||
return text; // base: no transformation
|
||||
}
|
||||
}
|
||||
20
02-structural/decorator/ProfanityFilterDecorator.java
Normal file
20
02-structural/decorator/ProfanityFilterDecorator.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package decorator;
|
||||
|
||||
/** Concrete Decorator 3: replaces bad words with asterisks. */
|
||||
public class ProfanityFilterDecorator extends TextDecorator {
|
||||
|
||||
private static final String[] BAD_WORDS = {"badword", "spam"};
|
||||
|
||||
public ProfanityFilterDecorator(TextProcessor wrapped) {
|
||||
super(wrapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String process(String text) {
|
||||
String result = super.process(text);
|
||||
for (String word : BAD_WORDS) {
|
||||
result = result.replaceAll("(?i)" + word, "*".repeat(word.length()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
25
02-structural/decorator/TextDecorator.java
Normal file
25
02-structural/decorator/TextDecorator.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package decorator;
|
||||
|
||||
/**
|
||||
* Base Decorator — holds a reference to the wrapped TextProcessor.
|
||||
* All concrete decorators extend this instead of implementing
|
||||
* TextProcessor directly. This avoids repeating the delegation
|
||||
* boilerplate in every decorator.
|
||||
*
|
||||
* Crucially: it delegates to the wrapped processor first,
|
||||
* then applies its own transformation to the result.
|
||||
*/
|
||||
public abstract class TextDecorator implements TextProcessor {
|
||||
|
||||
protected final TextProcessor wrapped;
|
||||
|
||||
protected TextDecorator(TextProcessor wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String process(String text) {
|
||||
// Delegate to the wrapped processor first, then let subclass apply its transform
|
||||
return wrapped.process(text);
|
||||
}
|
||||
}
|
||||
10
02-structural/decorator/TextProcessor.java
Normal file
10
02-structural/decorator/TextProcessor.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package decorator;
|
||||
|
||||
/**
|
||||
* Component interface — defines what all text processors do.
|
||||
* Both the concrete processor AND all decorators implement this.
|
||||
* This is what makes them stackable.
|
||||
*/
|
||||
public interface TextProcessor {
|
||||
String process(String text);
|
||||
}
|
||||
14
02-structural/decorator/TrimDecorator.java
Normal file
14
02-structural/decorator/TrimDecorator.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package decorator;
|
||||
|
||||
/** Concrete Decorator 2: trims leading/trailing whitespace. */
|
||||
public class TrimDecorator extends TextDecorator {
|
||||
|
||||
public TrimDecorator(TextProcessor wrapped) {
|
||||
super(wrapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String process(String text) {
|
||||
return super.process(text).trim();
|
||||
}
|
||||
}
|
||||
16
02-structural/decorator/UpperCaseDecorator.java
Normal file
16
02-structural/decorator/UpperCaseDecorator.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package decorator;
|
||||
|
||||
/** Concrete Decorator 1: converts all text to upper case. */
|
||||
public class UpperCaseDecorator extends TextDecorator {
|
||||
|
||||
public UpperCaseDecorator(TextProcessor wrapped) {
|
||||
super(wrapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String process(String text) {
|
||||
// Get the result from whatever is below us in the stack,
|
||||
// then apply OUR transformation on top of it.
|
||||
return super.process(text).toUpperCase();
|
||||
}
|
||||
}
|
||||
30
02-structural/facade/Main.java
Normal file
30
02-structural/facade/Main.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package facade;
|
||||
|
||||
/**
|
||||
* Facade Design Pattern — Runnable Demo
|
||||
*
|
||||
* Demonstrates reducing a complex video conversion subsystem
|
||||
* to a single method call via a Facade.
|
||||
*
|
||||
* Run: javac facade/*.java && java facade.Main
|
||||
* Article: https://ankurm.com/facade-design-pattern-java/
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Facade Design Pattern Demo ===\n");
|
||||
|
||||
VideoConversionFacade converter = new VideoConversionFacade();
|
||||
|
||||
System.out.println("-- Client: just one method call --");
|
||||
String result1 = converter.convertVideo("holiday.ogg", "mp4");
|
||||
System.out.println("Output: " + result1);
|
||||
|
||||
System.out.println();
|
||||
String result2 = converter.convertVideo("presentation.mp4", "ogg");
|
||||
System.out.println("Output: " + result2);
|
||||
|
||||
System.out.println("\nClient code: 1 line. Subsystem: 6 classes. Facade hides the complexity.");
|
||||
System.out.println("\n=== Demo complete ===");
|
||||
}
|
||||
}
|
||||
62
02-structural/facade/Subsystems.java
Normal file
62
02-structural/facade/Subsystems.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package facade;
|
||||
|
||||
/**
|
||||
* Complex subsystem classes — these are what the Facade hides.
|
||||
* Each class has its own complex API; clients shouldn't need to know all of them.
|
||||
*/
|
||||
|
||||
class VideoFile {
|
||||
private final String filename;
|
||||
private final String codecType;
|
||||
|
||||
VideoFile(String filename) {
|
||||
this(filename, filename.endsWith(".mp4") ? "mpeg4" : "ogg");
|
||||
}
|
||||
|
||||
VideoFile(String filename, String codec) {
|
||||
this.filename = filename;
|
||||
this.codecType = codec;
|
||||
System.out.println(" VideoFile: " + filename + " [codec: " + codecType + "]");
|
||||
}
|
||||
|
||||
public String getFilename() { return filename; }
|
||||
public String getCodecType() { return codecType; }
|
||||
}
|
||||
|
||||
interface Codec { String getName(); }
|
||||
|
||||
class MPEG4CompressionCodec implements Codec {
|
||||
@Override public String getName() { return "mpeg4"; }
|
||||
}
|
||||
|
||||
class OggCompressionCodec implements Codec {
|
||||
@Override public String getName() { return "ogg"; }
|
||||
}
|
||||
|
||||
class CodecFactory {
|
||||
static Codec extract(VideoFile file) {
|
||||
System.out.println(" CodecFactory: extracting codec from " + file.getFilename());
|
||||
if ("mpeg4".equals(file.getCodecType())) return new MPEG4CompressionCodec();
|
||||
return new OggCompressionCodec();
|
||||
}
|
||||
}
|
||||
|
||||
class BitrateReader {
|
||||
static VideoFile read(VideoFile file, Codec codec) {
|
||||
System.out.println(" BitrateReader: reading " + file.getFilename()
|
||||
+ " with codec " + codec.getName());
|
||||
return new VideoFile(file.getFilename());
|
||||
}
|
||||
|
||||
static VideoFile convert(VideoFile buffer, Codec codec) {
|
||||
System.out.println(" BitrateReader: converting to " + codec.getName());
|
||||
return new VideoFile(buffer.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
class AudioMixer {
|
||||
static VideoFile fix(VideoFile result) {
|
||||
System.out.println(" AudioMixer: fixing audio tracks");
|
||||
return new VideoFile(result.getFilename());
|
||||
}
|
||||
}
|
||||
39
02-structural/facade/VideoConversionFacade.java
Normal file
39
02-structural/facade/VideoConversionFacade.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package facade;
|
||||
|
||||
/**
|
||||
* Facade — the single, simple entry point to a complex video subsystem.
|
||||
*
|
||||
* Without this class, clients need to know about CodecFactory,
|
||||
* BitrateReader, AudioMixer, and VideoFile — 4 classes, dozens of methods.
|
||||
* The facade reduces that to ONE method call.
|
||||
*
|
||||
* The subsystem classes still exist and can be used directly
|
||||
* by advanced users who need fine-grained control.
|
||||
*/
|
||||
public class VideoConversionFacade {
|
||||
|
||||
public String convertVideo(String inputFile, String targetFormat) {
|
||||
System.out.println("VideoConversionFacade: starting conversion of " + inputFile);
|
||||
|
||||
// Step 1: open the file and detect codec
|
||||
VideoFile file = new VideoFile(inputFile);
|
||||
Codec sourceCodec = CodecFactory.extract(file);
|
||||
|
||||
// Step 2: prepare destination codec
|
||||
Codec destCodec;
|
||||
if ("mp4".equals(targetFormat)) {
|
||||
destCodec = new MPEG4CompressionCodec();
|
||||
} else {
|
||||
destCodec = new OggCompressionCodec();
|
||||
}
|
||||
|
||||
// Step 3: read, mix audio, encode
|
||||
VideoFile buffer = BitrateReader.read(file, sourceCodec);
|
||||
VideoFile intermediateResult = BitrateReader.convert(buffer, destCodec);
|
||||
VideoFile result = AudioMixer.fix(intermediateResult);
|
||||
|
||||
String outputFilename = inputFile.replaceAll("\\.[^.]+$", "." + targetFormat);
|
||||
System.out.println("VideoConversionFacade: conversion complete -> " + outputFilename);
|
||||
return outputFilename;
|
||||
}
|
||||
}
|
||||
50
02-structural/flyweight/Main.java
Normal file
50
02-structural/flyweight/Main.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package flyweight;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Flyweight Design Pattern — Runnable Demo
|
||||
*
|
||||
* Creates 1000 trees of only 3 species. Without Flyweight: 1000 TreeType
|
||||
* objects. With Flyweight: 3 TreeType objects (one per species), shared.
|
||||
*
|
||||
* Run: javac flyweight/*.java && java flyweight.Main
|
||||
* Article: https://ankurm.com/flyweight-design-pattern-java/
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Flyweight Design Pattern Demo ===\n");
|
||||
|
||||
List<Tree> forest = new ArrayList<>();
|
||||
Random rnd = new Random(42);
|
||||
|
||||
String[][] treeSpecs = {
|
||||
{"Oak", "dark-green", "rough-bark"},
|
||||
{"Pine", "blue-green", "needle-texture"},
|
||||
{"Birch","light-green","smooth-white-bark"}
|
||||
};
|
||||
|
||||
System.out.println("Creating 1,000 trees (only 3 TreeType objects should be created):");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
String[] spec = treeSpecs[i % 3];
|
||||
TreeType type = TreeFactory.getTreeType(spec[0], spec[1], spec[2]);
|
||||
forest.add(new Tree(rnd.nextInt(800), rnd.nextInt(600), type));
|
||||
}
|
||||
|
||||
System.out.println("\nForest created. Drawing first 5 trees:");
|
||||
for (int i = 0; i < 5; i++) {
|
||||
forest.get(i).draw();
|
||||
}
|
||||
|
||||
System.out.println("\n--- Memory summary ---");
|
||||
System.out.println("Trees in forest : " + forest.size());
|
||||
System.out.println("Unique TreeType objects in pool: " + TreeFactory.getPoolSize());
|
||||
System.out.println("Without Flyweight : 1,000 TreeType objects");
|
||||
System.out.println("With Flyweight : " + TreeFactory.getPoolSize() + " TreeType objects shared");
|
||||
|
||||
System.out.println("\n=== Demo complete ===");
|
||||
}
|
||||
}
|
||||
31
02-structural/flyweight/Tree.java
Normal file
31
02-structural/flyweight/Tree.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package flyweight;
|
||||
|
||||
/**
|
||||
* Context — stores the UNIQUE (extrinsic) state for each tree instance.
|
||||
* This is NOT the flyweight itself; it's the lightweight object that
|
||||
* holds position data and delegates rendering to a shared TreeType.
|
||||
*
|
||||
* 10,000 Tree objects × (x:4 bytes + y:4 bytes + reference:8 bytes) = ~160 KB
|
||||
* vs.
|
||||
* 10,000 Tree objects × (name + color + texture + x + y) = potentially MBs
|
||||
*/
|
||||
public class Tree {
|
||||
|
||||
// Extrinsic (unique) state — different per tree
|
||||
private final int x;
|
||||
private final int y;
|
||||
|
||||
// Reference to the SHARED flyweight
|
||||
private final TreeType type;
|
||||
|
||||
public Tree(int x, int y, TreeType type) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
// Passes extrinsic state (position) into the shared flyweight
|
||||
type.draw(x, y);
|
||||
}
|
||||
}
|
||||
29
02-structural/flyweight/TreeFactory.java
Normal file
29
02-structural/flyweight/TreeFactory.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package flyweight;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Flyweight Factory — the cache that ensures each unique TreeType
|
||||
* is only created once, no matter how many trees use it.
|
||||
*
|
||||
* This is the piece that makes Flyweight work:
|
||||
* it intercepts creation requests and returns an existing
|
||||
* shared instance if one already exists.
|
||||
*/
|
||||
public class TreeFactory {
|
||||
|
||||
// The pool of shared flyweights
|
||||
private static final Map<String, TreeType> treeTypes = new HashMap<>();
|
||||
|
||||
public static TreeType getTreeType(String name, String color, String texture) {
|
||||
String key = name + "_" + color + "_" + texture;
|
||||
|
||||
// Only create a new TreeType if we haven't seen this combination before
|
||||
return treeTypes.computeIfAbsent(key, k -> new TreeType(name, color, texture));
|
||||
}
|
||||
|
||||
public static int getPoolSize() {
|
||||
return treeTypes.size();
|
||||
}
|
||||
}
|
||||
29
02-structural/flyweight/TreeType.java
Normal file
29
02-structural/flyweight/TreeType.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package flyweight;
|
||||
|
||||
/**
|
||||
* Flyweight — holds the SHARED (intrinsic) state.
|
||||
* TreeType is shared between all trees of the same species.
|
||||
*
|
||||
* If you have 10,000 oak trees, there's ONE OakType object in memory.
|
||||
* Each individual tree only stores its unique position (extrinsic state).
|
||||
*/
|
||||
public class TreeType {
|
||||
|
||||
// Intrinsic (shared) state — same for all trees of this species
|
||||
private final String name;
|
||||
private final String color;
|
||||
private final String texture; // imagine a large texture bitmap here
|
||||
|
||||
public TreeType(String name, String color, String texture) {
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
this.texture = texture;
|
||||
System.out.println(" [TreeType created: " + name + "]"); // see how few are created
|
||||
}
|
||||
|
||||
// Extrinsic state (x, y) is passed IN at render time — NOT stored here
|
||||
public void draw(int x, int y) {
|
||||
System.out.printf(" Drawing %s tree [%s/%s] at (%d, %d)%n",
|
||||
name, color, texture, x, y);
|
||||
}
|
||||
}
|
||||
11
02-structural/proxy/DatabaseConnection.java
Normal file
11
02-structural/proxy/DatabaseConnection.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package proxy;
|
||||
|
||||
/**
|
||||
* Subject interface — defines what both the real object and proxy expose.
|
||||
* Clients depend on this, not on the concrete class.
|
||||
*/
|
||||
public interface DatabaseConnection {
|
||||
void connect();
|
||||
String executeQuery(String sql);
|
||||
void disconnect();
|
||||
}
|
||||
52
02-structural/proxy/LazyConnectionProxy.java
Normal file
52
02-structural/proxy/LazyConnectionProxy.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package proxy;
|
||||
|
||||
/**
|
||||
* Virtual Proxy — delays creating the RealDatabaseConnection until
|
||||
* the first actual query is made. If no query is ever made (e.g.,
|
||||
* the service is initialized but never used in this request),
|
||||
* the expensive connection is never opened.
|
||||
*
|
||||
* This is exactly how Hibernate proxies work: entities are not
|
||||
* loaded from the database until you access a field on them.
|
||||
*/
|
||||
public class LazyConnectionProxy implements DatabaseConnection {
|
||||
|
||||
private final String url;
|
||||
private RealDatabaseConnection real; // null until first use
|
||||
|
||||
public LazyConnectionProxy(String url) {
|
||||
this.url = url;
|
||||
System.out.println("[Proxy] Created for " + url + " (real connection NOT opened yet)");
|
||||
}
|
||||
|
||||
// Lazy initialization — create and connect only on first real need
|
||||
private void initIfNeeded() {
|
||||
if (real == null) {
|
||||
System.out.println("[Proxy] First access — initializing real connection...");
|
||||
real = new RealDatabaseConnection(url);
|
||||
real.connect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
// Proxy absorbs the connect() call — real connection opened lazily
|
||||
System.out.println("[Proxy] connect() called — deferring to first query");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String executeQuery(String sql) {
|
||||
initIfNeeded(); // NOW we actually need the connection
|
||||
return real.executeQuery(sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (real != null) {
|
||||
real.disconnect();
|
||||
real = null;
|
||||
} else {
|
||||
System.out.println("[Proxy] disconnect() called but connection was never opened");
|
||||
}
|
||||
}
|
||||
}
|
||||
41
02-structural/proxy/LoggingProxy.java
Normal file
41
02-structural/proxy/LoggingProxy.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package proxy;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Logging Proxy — adds timing and audit logging around every query
|
||||
* without touching RealDatabaseConnection or any of its callers.
|
||||
*
|
||||
* This is the "cross-cutting concern" use case of Proxy,
|
||||
* the same mechanism behind Spring AOP's @Around advice.
|
||||
*/
|
||||
public class LoggingProxy implements DatabaseConnection {
|
||||
|
||||
private final DatabaseConnection target;
|
||||
|
||||
public LoggingProxy(DatabaseConnection target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
System.out.println("[Log] connect() at " + Instant.now());
|
||||
target.connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String executeQuery(String sql) {
|
||||
long start = System.currentTimeMillis();
|
||||
System.out.println("[Log] QUERY START: " + sql);
|
||||
String result = target.executeQuery(sql);
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
System.out.println("[Log] QUERY END: " + elapsed + "ms | result: " + result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
System.out.println("[Log] disconnect() at " + Instant.now());
|
||||
target.disconnect();
|
||||
}
|
||||
}
|
||||
51
02-structural/proxy/Main.java
Normal file
51
02-structural/proxy/Main.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package proxy;
|
||||
|
||||
/**
|
||||
* Proxy Design Pattern — Runnable Demo
|
||||
*
|
||||
* Shows two proxy types:
|
||||
* 1. Virtual Proxy (lazy connection)
|
||||
* 2. Logging Proxy (cross-cutting concern)
|
||||
* 3. Proxy chaining (both together)
|
||||
*
|
||||
* Run: javac proxy/*.java && java proxy.Main
|
||||
* Article: https://ankurm.com/proxy-design-pattern-java/
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
System.out.println("=== Proxy Design Pattern Demo ===\n");
|
||||
|
||||
// --- Virtual Proxy: lazy connection ---
|
||||
System.out.println("-- Virtual Proxy (lazy loading) --");
|
||||
DatabaseConnection lazy = new LazyConnectionProxy("jdbc:postgresql://localhost/mydb");
|
||||
lazy.connect(); // absorbed by proxy, no real connection yet
|
||||
System.out.println("(no real connection yet — saved startup time)");
|
||||
System.out.println("Result: " + lazy.executeQuery("SELECT * FROM users WHERE id=1"));
|
||||
System.out.println("Result: " + lazy.executeQuery("SELECT COUNT(*) FROM orders"));
|
||||
lazy.disconnect();
|
||||
|
||||
System.out.println();
|
||||
|
||||
// --- Logging Proxy: wraps the real connection ---
|
||||
System.out.println("-- Logging Proxy --");
|
||||
DatabaseConnection real = new RealDatabaseConnection("jdbc:mysql://localhost/shopdb");
|
||||
real.connect();
|
||||
DatabaseConnection logged = new LoggingProxy(real);
|
||||
logged.executeQuery("SELECT * FROM products LIMIT 10");
|
||||
logged.disconnect();
|
||||
|
||||
System.out.println();
|
||||
|
||||
// --- Proxy chaining: lazy + logging ---
|
||||
System.out.println("-- Chained Proxies: Lazy + Logging --");
|
||||
DatabaseConnection chain =
|
||||
new LoggingProxy(
|
||||
new LazyConnectionProxy("jdbc:oracle://localhost/warehouse"));
|
||||
chain.connect();
|
||||
chain.executeQuery("SELECT SUM(quantity) FROM inventory");
|
||||
chain.disconnect();
|
||||
|
||||
System.out.println("\n=== Demo complete ===");
|
||||
}
|
||||
}
|
||||
33
02-structural/proxy/RealDatabaseConnection.java
Normal file
33
02-structural/proxy/RealDatabaseConnection.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package proxy;
|
||||
|
||||
/**
|
||||
* Real Subject — the actual, expensive database connection.
|
||||
* Opening it takes time. We want to delay this until truly needed.
|
||||
*/
|
||||
public class RealDatabaseConnection implements DatabaseConnection {
|
||||
|
||||
private final String url;
|
||||
|
||||
public RealDatabaseConnection(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
System.out.println("[Real DB] Connecting to " + url + " (expensive operation)...");
|
||||
// Simulate connection setup time
|
||||
try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
|
||||
System.out.println("[Real DB] Connected.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String executeQuery(String sql) {
|
||||
System.out.println("[Real DB] Executing: " + sql);
|
||||
return "ResultSet{rows=42}"; // simulated result
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
System.out.println("[Real DB] Disconnecting from " + url);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
15
03-behavioral/chain-of-responsibility/Level1Support.java
Normal file
15
03-behavioral/chain-of-responsibility/Level1Support.java
Normal 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);
|
||||
}
|
||||
}
|
||||
15
03-behavioral/chain-of-responsibility/Level2Support.java
Normal file
15
03-behavioral/chain-of-responsibility/Level2Support.java
Normal 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);
|
||||
}
|
||||
}
|
||||
15
03-behavioral/chain-of-responsibility/Level3Support.java
Normal file
15
03-behavioral/chain-of-responsibility/Level3Support.java
Normal 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);
|
||||
}
|
||||
}
|
||||
38
03-behavioral/chain-of-responsibility/Main.java
Normal file
38
03-behavioral/chain-of-responsibility/Main.java
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
31
03-behavioral/chain-of-responsibility/SupportHandler.java
Normal file
31
03-behavioral/chain-of-responsibility/SupportHandler.java
Normal 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);
|
||||
}
|
||||
21
03-behavioral/chain-of-responsibility/SupportTicket.java
Normal file
21
03-behavioral/chain-of-responsibility/SupportTicket.java
Normal 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;
|
||||
}
|
||||
}
|
||||
8
03-behavioral/command/Command.java
Normal file
8
03-behavioral/command/Command.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package command;
|
||||
|
||||
/** Command interface: every action is an object */
|
||||
public interface Command {
|
||||
void execute();
|
||||
void undo();
|
||||
String getDescription();
|
||||
}
|
||||
28
03-behavioral/command/CommandHistory.java
Normal file
28
03-behavioral/command/CommandHistory.java
Normal 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());
|
||||
}
|
||||
}
|
||||
23
03-behavioral/command/DeleteCommand.java
Normal file
23
03-behavioral/command/DeleteCommand.java
Normal 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; }
|
||||
}
|
||||
17
03-behavioral/command/InsertCommand.java
Normal file
17
03-behavioral/command/InsertCommand.java
Normal 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; }
|
||||
}
|
||||
40
03-behavioral/command/Main.java
Normal file
40
03-behavioral/command/Main.java
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
21
03-behavioral/command/TextEditor.java
Normal file
21
03-behavioral/command/TextEditor.java
Normal 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 + "\"]"; }
|
||||
}
|
||||
19
03-behavioral/interpreter/AddExpression.java
Normal file
19
03-behavioral/interpreter/AddExpression.java
Normal 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();
|
||||
}
|
||||
}
|
||||
9
03-behavioral/interpreter/Expression.java
Normal file
9
03-behavioral/interpreter/Expression.java
Normal 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();
|
||||
}
|
||||
38
03-behavioral/interpreter/Main.java
Normal file
38
03-behavioral/interpreter/Main.java
Normal 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());
|
||||
}
|
||||
}
|
||||
19
03-behavioral/interpreter/MultiplyExpression.java
Normal file
19
03-behavioral/interpreter/MultiplyExpression.java
Normal 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();
|
||||
}
|
||||
}
|
||||
18
03-behavioral/interpreter/NumberExpression.java
Normal file
18
03-behavioral/interpreter/NumberExpression.java
Normal 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;
|
||||
}
|
||||
}
|
||||
19
03-behavioral/interpreter/SubtractExpression.java
Normal file
19
03-behavioral/interpreter/SubtractExpression.java
Normal 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();
|
||||
}
|
||||
}
|
||||
14
03-behavioral/iterator/Book.java
Normal file
14
03-behavioral/iterator/Book.java
Normal 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 + ")"; }
|
||||
}
|
||||
7
03-behavioral/iterator/BookIterator.java
Normal file
7
03-behavioral/iterator/BookIterator.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package iterator;
|
||||
|
||||
/** Our custom Iterator interface (mirrors java.util.Iterator) */
|
||||
public interface BookIterator {
|
||||
boolean hasNext();
|
||||
Book next();
|
||||
}
|
||||
57
03-behavioral/iterator/BookShelf.java
Normal file
57
03-behavioral/iterator/BookShelf.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
03-behavioral/iterator/Main.java
Normal file
37
03-behavioral/iterator/Main.java
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
7
03-behavioral/mediator/ChatMediator.java
Normal file
7
03-behavioral/mediator/ChatMediator.java
Normal 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);
|
||||
}
|
||||
27
03-behavioral/mediator/ChatRoom.java
Normal file
27
03-behavioral/mediator/ChatRoom.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
03-behavioral/mediator/Main.java
Normal file
35
03-behavioral/mediator/Main.java
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
26
03-behavioral/mediator/User.java
Normal file
26
03-behavioral/mediator/User.java
Normal 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);
|
||||
}
|
||||
}
|
||||
43
03-behavioral/memento/Editor.java
Normal file
43
03-behavioral/memento/Editor.java
Normal 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 + "]";
|
||||
}
|
||||
}
|
||||
28
03-behavioral/memento/EditorMemento.java
Normal file
28
03-behavioral/memento/EditorMemento.java
Normal 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 + "]";
|
||||
}
|
||||
}
|
||||
24
03-behavioral/memento/History.java
Normal file
24
03-behavioral/memento/History.java
Normal 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(); }
|
||||
}
|
||||
38
03-behavioral/memento/Main.java
Normal file
38
03-behavioral/memento/Main.java
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
22
03-behavioral/observer/AlertObserver.java
Normal file
22
03-behavioral/observer/AlertObserver.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
03-behavioral/observer/Main.java
Normal file
36
03-behavioral/observer/Main.java
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
19
03-behavioral/observer/PortfolioObserver.java
Normal file
19
03-behavioral/observer/PortfolioObserver.java
Normal 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);
|
||||
}
|
||||
}
|
||||
36
03-behavioral/observer/StockMarket.java
Normal file
36
03-behavioral/observer/StockMarket.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
03-behavioral/observer/StockObserver.java
Normal file
6
03-behavioral/observer/StockObserver.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package observer;
|
||||
|
||||
/** Observer interface — all subscribers implement this */
|
||||
public interface StockObserver {
|
||||
void onPriceChanged(String ticker, double oldPrice, double newPrice);
|
||||
}
|
||||
11
03-behavioral/state/GreenState.java
Normal file
11
03-behavioral/state/GreenState.java
Normal 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"; }
|
||||
}
|
||||
26
03-behavioral/state/Main.java
Normal file
26
03-behavioral/state/Main.java
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
11
03-behavioral/state/RedState.java
Normal file
11
03-behavioral/state/RedState.java
Normal 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"; }
|
||||
}
|
||||
21
03-behavioral/state/TrafficLight.java
Normal file
21
03-behavioral/state/TrafficLight.java
Normal 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(); }
|
||||
}
|
||||
8
03-behavioral/state/TrafficLightState.java
Normal file
8
03-behavioral/state/TrafficLightState.java
Normal 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();
|
||||
}
|
||||
11
03-behavioral/state/YellowState.java
Normal file
11
03-behavioral/state/YellowState.java
Normal 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"; }
|
||||
}
|
||||
12
03-behavioral/strategy/BubbleSort.java
Normal file
12
03-behavioral/strategy/BubbleSort.java
Normal 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"; }
|
||||
}
|
||||
37
03-behavioral/strategy/Main.java
Normal file
37
03-behavioral/strategy/Main.java
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
30
03-behavioral/strategy/MergeSort.java
Normal file
30
03-behavioral/strategy/MergeSort.java
Normal 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"; }
|
||||
}
|
||||
26
03-behavioral/strategy/QuickSort.java
Normal file
26
03-behavioral/strategy/QuickSort.java
Normal 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"; }
|
||||
}
|
||||
7
03-behavioral/strategy/SortStrategy.java
Normal file
7
03-behavioral/strategy/SortStrategy.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package strategy;
|
||||
|
||||
/** Strategy interface — all sorting algorithms implement this */
|
||||
public interface SortStrategy {
|
||||
void sort(int[] data);
|
||||
String getName();
|
||||
}
|
||||
24
03-behavioral/strategy/Sorter.java
Normal file
24
03-behavioral/strategy/Sorter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
31
03-behavioral/template-method/ApiMigration.java
Normal file
31
03-behavioral/template-method/ApiMigration.java
Normal 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; }
|
||||
}
|
||||
27
03-behavioral/template-method/CsvMigration.java
Normal file
27
03-behavioral/template-method/CsvMigration.java
Normal 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
|
||||
}
|
||||
}
|
||||
50
03-behavioral/template-method/DataMigration.java
Normal file
50
03-behavioral/template-method/DataMigration.java
Normal 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.");
|
||||
}
|
||||
}
|
||||
24
03-behavioral/template-method/Main.java
Normal file
24
03-behavioral/template-method/Main.java
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
26
03-behavioral/visitor/AreaCalculator.java
Normal file
26
03-behavioral/visitor/AreaCalculator.java
Normal 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);
|
||||
}
|
||||
}
|
||||
9
03-behavioral/visitor/Circle.java
Normal file
9
03-behavioral/visitor/Circle.java
Normal 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); }
|
||||
}
|
||||
38
03-behavioral/visitor/Main.java
Normal file
38
03-behavioral/visitor/Main.java
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
28
03-behavioral/visitor/PerimeterCalculator.java
Normal file
28
03-behavioral/visitor/PerimeterCalculator.java
Normal 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);
|
||||
}
|
||||
}
|
||||
10
03-behavioral/visitor/Rectangle.java
Normal file
10
03-behavioral/visitor/Rectangle.java
Normal 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); }
|
||||
}
|
||||
7
03-behavioral/visitor/Shape.java
Normal file
7
03-behavioral/visitor/Shape.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package visitor;
|
||||
|
||||
/** Element interface — accepts a visitor */
|
||||
public interface Shape {
|
||||
void accept(ShapeVisitor visitor);
|
||||
String getName();
|
||||
}
|
||||
8
03-behavioral/visitor/ShapeVisitor.java
Normal file
8
03-behavioral/visitor/ShapeVisitor.java
Normal 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);
|
||||
}
|
||||
10
03-behavioral/visitor/Triangle.java
Normal file
10
03-behavioral/visitor/Triangle.java
Normal 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); }
|
||||
}
|
||||
2
PUSH-TO-GITEA.bat
Normal file
2
PUSH-TO-GITEA.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
powershell -ExecutionPolicy Bypass -NoExit -File "%~dp0push-to-gitea.ps1"
|
||||
98
README.md
Normal file
98
README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# GoF Design Patterns in Java
|
||||
|
||||
Complete runnable Java 17 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.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
design-patterns/
|
||||
├── 01-creational/
|
||||
│ ├── factory-method/
|
||||
│ ├── abstract-factory/
|
||||
│ ├── builder/
|
||||
│ ├── prototype/
|
||||
│ └── singleton/
|
||||
├── 02-structural/
|
||||
│ ├── adapter/
|
||||
│ ├── bridge/
|
||||
│ ├── composite/
|
||||
│ ├── decorator/
|
||||
│ ├── facade/
|
||||
│ ├── flyweight/
|
||||
│ └── proxy/
|
||||
└── 03-behavioral/
|
||||
├── chain-of-responsibility/
|
||||
├── command/
|
||||
├── interpreter/
|
||||
├── iterator/
|
||||
├── mediator/
|
||||
├── memento/
|
||||
├── observer/
|
||||
├── state/
|
||||
├── strategy/
|
||||
├── template-method/
|
||||
└── visitor/
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Java 17+ (Eclipse Temurin recommended)
|
||||
- No build tool required — plain `javac` / `java`
|
||||
|
||||
## Run any pattern
|
||||
|
||||
```powershell
|
||||
# Windows (PowerShell)
|
||||
$JAVAC = 'C:\Program Files\Eclipse Adoptium\jdk-17.0.7.7-hotspot\bin\javac.exe'
|
||||
$JAVA = 'C:\Program Files\Eclipse Adoptium\jdk-17.0.7.7-hotspot\bin\java.exe'
|
||||
|
||||
& $JAVAC 03-behavioral/strategy/*.java -d out/strategy
|
||||
& $JAVA -cp out/strategy strategy.Main
|
||||
```
|
||||
|
||||
```bash
|
||||
# Linux / macOS
|
||||
javac 03-behavioral/strategy/*.java -d out/strategy
|
||||
java -cp out/strategy strategy.Main
|
||||
```
|
||||
|
||||
## Run all behavioral patterns (Windows)
|
||||
|
||||
Double-click `RUN-behavioral.bat` or run in PowerShell:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File run-behavioral.ps1
|
||||
```
|
||||
|
||||
## Articles
|
||||
|
||||
| Pattern | Category | Article |
|
||||
|---------|----------|---------|
|
||||
| Factory Method | Creational | https://ankurm.com/factory-method-design-pattern-java/ |
|
||||
| Abstract Factory | Creational | https://ankurm.com/abstract-factory-design-pattern-java/ |
|
||||
| Builder | Creational | https://ankurm.com/builder-design-pattern-java/ |
|
||||
| Prototype | Creational | https://ankurm.com/prototype-design-pattern-java/ |
|
||||
| Singleton | Creational | https://ankurm.com/singleton-design-pattern-java/ |
|
||||
| Adapter | Structural | https://ankurm.com/adapter-design-pattern-java/ |
|
||||
| Bridge | Structural | https://ankurm.com/bridge-design-pattern-java/ |
|
||||
| Composite | Structural | https://ankurm.com/composite-design-pattern-java/ |
|
||||
| Decorator | Structural | https://ankurm.com/decorator-design-pattern-java/ |
|
||||
| Facade | Structural | https://ankurm.com/facade-design-pattern-java/ |
|
||||
| Flyweight | Structural | https://ankurm.com/flyweight-design-pattern-java/ |
|
||||
| Proxy | Structural | https://ankurm.com/proxy-design-pattern-java/ |
|
||||
| Chain of Responsibility | Behavioral | https://ankurm.com/chain-of-responsibility-design-pattern-java/ |
|
||||
| Command | Behavioral | https://ankurm.com/command-design-pattern-java/ |
|
||||
| Interpreter | Behavioral | https://ankurm.com/interpreter-design-pattern-java/ |
|
||||
| Iterator | Behavioral | https://ankurm.com/iterator-design-pattern-java/ |
|
||||
| Mediator | Behavioral | https://ankurm.com/mediator-design-pattern-java/ |
|
||||
| Memento | Behavioral | https://ankurm.com/memento-design-pattern-java/ |
|
||||
| Observer | Behavioral | https://ankurm.com/observer-design-pattern-java/ |
|
||||
| State | Behavioral | https://ankurm.com/state-design-pattern-java/ |
|
||||
| Strategy | Behavioral | https://ankurm.com/strategy-design-pattern-java/ |
|
||||
| Template Method | Behavioral | https://ankurm.com/template-method-design-pattern-java/ |
|
||||
| Visitor | Behavioral | https://ankurm.com/visitor-design-pattern-java/ |
|
||||
|
||||
## Reference
|
||||
|
||||
- *Design Patterns: Elements of Reusable Object-Oriented Software* — Gamma, Helm, Johnson, Vlissides
|
||||
- https://refactoring.guru/design-patterns
|
||||
2
RUN-behavioral.bat
Normal file
2
RUN-behavioral.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
powershell -ExecutionPolicy Bypass -NoExit -Command "& 'C:\Users\Ankur\Claude\Projects\@ankurm Blog\design-patterns\run-behavioral.ps1'"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user