From a5beb614250f52a3b5f5e6f171acf2188ce70493 Mon Sep 17 00:00:00 2001 From: Ankur Date: Sat, 13 Jun 2026 21:44:56 +0530 Subject: [PATCH] Add all 23 GoF design pattern implementations (2026-06-13) --- .gitignore | 4 + 02-structural/adapter/Main.java | 38 +++++++ 02-structural/adapter/OrderService.java | 31 ++++++ 02-structural/adapter/PaymentGateway.java | 12 +++ 02-structural/adapter/README.md | 33 +++++++ 02-structural/adapter/StripeClient.java | 51 ++++++++++ .../adapter/StripePaymentAdapter.java | 44 +++++++++ 02-structural/bridge/AdvancedRemote.java | 25 +++++ 02-structural/bridge/Device.java | 17 ++++ 02-structural/bridge/Main.java | 47 +++++++++ 02-structural/bridge/README.md | 30 ++++++ 02-structural/bridge/Radio.java | 29 ++++++ 02-structural/bridge/RemoteControl.java | 33 +++++++ 02-structural/bridge/TV.java | 29 ++++++ 02-structural/composite/Directory.java | 53 ++++++++++ 02-structural/composite/File.java | 27 +++++ 02-structural/composite/FileSystemItem.java | 12 +++ 02-structural/composite/Main.java | 58 +++++++++++ 02-structural/decorator/Main.java | 53 ++++++++++ .../decorator/PlainTextProcessor.java | 14 +++ .../decorator/ProfanityFilterDecorator.java | 20 ++++ 02-structural/decorator/TextDecorator.java | 25 +++++ 02-structural/decorator/TextProcessor.java | 10 ++ 02-structural/decorator/TrimDecorator.java | 14 +++ .../decorator/UpperCaseDecorator.java | 16 +++ 02-structural/facade/Main.java | 30 ++++++ 02-structural/facade/Subsystems.java | 62 ++++++++++++ .../facade/VideoConversionFacade.java | 39 ++++++++ 02-structural/flyweight/Main.java | 50 ++++++++++ 02-structural/flyweight/Tree.java | 31 ++++++ 02-structural/flyweight/TreeFactory.java | 29 ++++++ 02-structural/flyweight/TreeType.java | 29 ++++++ 02-structural/proxy/DatabaseConnection.java | 11 +++ 02-structural/proxy/LazyConnectionProxy.java | 52 ++++++++++ 02-structural/proxy/LoggingProxy.java | 41 ++++++++ 02-structural/proxy/Main.java | 51 ++++++++++ .../proxy/RealDatabaseConnection.java | 33 +++++++ .../CriticalIncidentTeam.java | 15 +++ .../Level1Support.java | 15 +++ .../Level2Support.java | 15 +++ .../Level3Support.java | 15 +++ .../chain-of-responsibility/Main.java | 38 +++++++ .../SupportHandler.java | 31 ++++++ .../SupportTicket.java | 21 ++++ 03-behavioral/command/Command.java | 8 ++ 03-behavioral/command/CommandHistory.java | 28 ++++++ 03-behavioral/command/DeleteCommand.java | 23 +++++ 03-behavioral/command/InsertCommand.java | 17 ++++ 03-behavioral/command/Main.java | 40 ++++++++ 03-behavioral/command/TextEditor.java | 21 ++++ 03-behavioral/interpreter/AddExpression.java | 19 ++++ 03-behavioral/interpreter/Expression.java | 9 ++ 03-behavioral/interpreter/Main.java | 38 +++++++ .../interpreter/MultiplyExpression.java | 19 ++++ .../interpreter/NumberExpression.java | 18 ++++ .../interpreter/SubtractExpression.java | 19 ++++ 03-behavioral/iterator/Book.java | 14 +++ 03-behavioral/iterator/BookIterator.java | 7 ++ 03-behavioral/iterator/BookShelf.java | 57 +++++++++++ 03-behavioral/iterator/Main.java | 37 +++++++ 03-behavioral/mediator/ChatMediator.java | 7 ++ 03-behavioral/mediator/ChatRoom.java | 27 +++++ 03-behavioral/mediator/Main.java | 35 +++++++ 03-behavioral/mediator/User.java | 26 +++++ 03-behavioral/memento/Editor.java | 43 ++++++++ 03-behavioral/memento/EditorMemento.java | 28 ++++++ 03-behavioral/memento/History.java | 24 +++++ 03-behavioral/memento/Main.java | 38 +++++++ 03-behavioral/observer/AlertObserver.java | 22 +++++ 03-behavioral/observer/Main.java | 36 +++++++ 03-behavioral/observer/PortfolioObserver.java | 19 ++++ 03-behavioral/observer/StockMarket.java | 36 +++++++ 03-behavioral/observer/StockObserver.java | 6 ++ 03-behavioral/state/GreenState.java | 11 +++ 03-behavioral/state/Main.java | 26 +++++ 03-behavioral/state/RedState.java | 11 +++ 03-behavioral/state/TrafficLight.java | 21 ++++ 03-behavioral/state/TrafficLightState.java | 8 ++ 03-behavioral/state/YellowState.java | 11 +++ 03-behavioral/strategy/BubbleSort.java | 12 +++ 03-behavioral/strategy/Main.java | 37 +++++++ 03-behavioral/strategy/MergeSort.java | 30 ++++++ 03-behavioral/strategy/QuickSort.java | 26 +++++ 03-behavioral/strategy/SortStrategy.java | 7 ++ 03-behavioral/strategy/Sorter.java | 24 +++++ .../template-method/ApiMigration.java | 31 ++++++ .../template-method/CsvMigration.java | 27 +++++ .../template-method/DataMigration.java | 50 ++++++++++ 03-behavioral/template-method/Main.java | 24 +++++ 03-behavioral/visitor/AreaCalculator.java | 26 +++++ 03-behavioral/visitor/Circle.java | 9 ++ 03-behavioral/visitor/Main.java | 38 +++++++ .../visitor/PerimeterCalculator.java | 28 ++++++ 03-behavioral/visitor/Rectangle.java | 10 ++ 03-behavioral/visitor/Shape.java | 7 ++ 03-behavioral/visitor/ShapeVisitor.java | 8 ++ 03-behavioral/visitor/Triangle.java | 10 ++ PUSH-TO-GITEA.bat | 2 + README.md | 98 +++++++++++++++++++ RUN-behavioral.bat | 2 + RUN.bat | 2 + compile-and-run-structural.bat | 74 ++++++++++++++ push-to-gitea.ps1 | 50 ++++++++++ run-all.bat | 80 +++++++++++++++ run-behavioral.ps1 | 62 ++++++++++++ run-structural.ps1 | 61 ++++++++++++ 106 files changed, 2977 insertions(+) create mode 100644 .gitignore create mode 100644 02-structural/adapter/Main.java create mode 100644 02-structural/adapter/OrderService.java create mode 100644 02-structural/adapter/PaymentGateway.java create mode 100644 02-structural/adapter/README.md create mode 100644 02-structural/adapter/StripeClient.java create mode 100644 02-structural/adapter/StripePaymentAdapter.java create mode 100644 02-structural/bridge/AdvancedRemote.java create mode 100644 02-structural/bridge/Device.java create mode 100644 02-structural/bridge/Main.java create mode 100644 02-structural/bridge/README.md create mode 100644 02-structural/bridge/Radio.java create mode 100644 02-structural/bridge/RemoteControl.java create mode 100644 02-structural/bridge/TV.java create mode 100644 02-structural/composite/Directory.java create mode 100644 02-structural/composite/File.java create mode 100644 02-structural/composite/FileSystemItem.java create mode 100644 02-structural/composite/Main.java create mode 100644 02-structural/decorator/Main.java create mode 100644 02-structural/decorator/PlainTextProcessor.java create mode 100644 02-structural/decorator/ProfanityFilterDecorator.java create mode 100644 02-structural/decorator/TextDecorator.java create mode 100644 02-structural/decorator/TextProcessor.java create mode 100644 02-structural/decorator/TrimDecorator.java create mode 100644 02-structural/decorator/UpperCaseDecorator.java create mode 100644 02-structural/facade/Main.java create mode 100644 02-structural/facade/Subsystems.java create mode 100644 02-structural/facade/VideoConversionFacade.java create mode 100644 02-structural/flyweight/Main.java create mode 100644 02-structural/flyweight/Tree.java create mode 100644 02-structural/flyweight/TreeFactory.java create mode 100644 02-structural/flyweight/TreeType.java create mode 100644 02-structural/proxy/DatabaseConnection.java create mode 100644 02-structural/proxy/LazyConnectionProxy.java create mode 100644 02-structural/proxy/LoggingProxy.java create mode 100644 02-structural/proxy/Main.java create mode 100644 02-structural/proxy/RealDatabaseConnection.java create mode 100644 03-behavioral/chain-of-responsibility/CriticalIncidentTeam.java create mode 100644 03-behavioral/chain-of-responsibility/Level1Support.java create mode 100644 03-behavioral/chain-of-responsibility/Level2Support.java create mode 100644 03-behavioral/chain-of-responsibility/Level3Support.java create mode 100644 03-behavioral/chain-of-responsibility/Main.java create mode 100644 03-behavioral/chain-of-responsibility/SupportHandler.java create mode 100644 03-behavioral/chain-of-responsibility/SupportTicket.java create mode 100644 03-behavioral/command/Command.java create mode 100644 03-behavioral/command/CommandHistory.java create mode 100644 03-behavioral/command/DeleteCommand.java create mode 100644 03-behavioral/command/InsertCommand.java create mode 100644 03-behavioral/command/Main.java create mode 100644 03-behavioral/command/TextEditor.java create mode 100644 03-behavioral/interpreter/AddExpression.java create mode 100644 03-behavioral/interpreter/Expression.java create mode 100644 03-behavioral/interpreter/Main.java create mode 100644 03-behavioral/interpreter/MultiplyExpression.java create mode 100644 03-behavioral/interpreter/NumberExpression.java create mode 100644 03-behavioral/interpreter/SubtractExpression.java create mode 100644 03-behavioral/iterator/Book.java create mode 100644 03-behavioral/iterator/BookIterator.java create mode 100644 03-behavioral/iterator/BookShelf.java create mode 100644 03-behavioral/iterator/Main.java create mode 100644 03-behavioral/mediator/ChatMediator.java create mode 100644 03-behavioral/mediator/ChatRoom.java create mode 100644 03-behavioral/mediator/Main.java create mode 100644 03-behavioral/mediator/User.java create mode 100644 03-behavioral/memento/Editor.java create mode 100644 03-behavioral/memento/EditorMemento.java create mode 100644 03-behavioral/memento/History.java create mode 100644 03-behavioral/memento/Main.java create mode 100644 03-behavioral/observer/AlertObserver.java create mode 100644 03-behavioral/observer/Main.java create mode 100644 03-behavioral/observer/PortfolioObserver.java create mode 100644 03-behavioral/observer/StockMarket.java create mode 100644 03-behavioral/observer/StockObserver.java create mode 100644 03-behavioral/state/GreenState.java create mode 100644 03-behavioral/state/Main.java create mode 100644 03-behavioral/state/RedState.java create mode 100644 03-behavioral/state/TrafficLight.java create mode 100644 03-behavioral/state/TrafficLightState.java create mode 100644 03-behavioral/state/YellowState.java create mode 100644 03-behavioral/strategy/BubbleSort.java create mode 100644 03-behavioral/strategy/Main.java create mode 100644 03-behavioral/strategy/MergeSort.java create mode 100644 03-behavioral/strategy/QuickSort.java create mode 100644 03-behavioral/strategy/SortStrategy.java create mode 100644 03-behavioral/strategy/Sorter.java create mode 100644 03-behavioral/template-method/ApiMigration.java create mode 100644 03-behavioral/template-method/CsvMigration.java create mode 100644 03-behavioral/template-method/DataMigration.java create mode 100644 03-behavioral/template-method/Main.java create mode 100644 03-behavioral/visitor/AreaCalculator.java create mode 100644 03-behavioral/visitor/Circle.java create mode 100644 03-behavioral/visitor/Main.java create mode 100644 03-behavioral/visitor/PerimeterCalculator.java create mode 100644 03-behavioral/visitor/Rectangle.java create mode 100644 03-behavioral/visitor/Shape.java create mode 100644 03-behavioral/visitor/ShapeVisitor.java create mode 100644 03-behavioral/visitor/Triangle.java create mode 100644 PUSH-TO-GITEA.bat create mode 100644 README.md create mode 100644 RUN-behavioral.bat create mode 100644 RUN.bat create mode 100644 compile-and-run-structural.bat create mode 100644 push-to-gitea.ps1 create mode 100644 run-all.bat create mode 100644 run-behavioral.ps1 create mode 100644 run-structural.ps1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec352c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +out/ +*.class +screenshots/*.png +*.b64.txt diff --git a/02-structural/adapter/Main.java b/02-structural/adapter/Main.java new file mode 100644 index 0000000..b41cfca --- /dev/null +++ b/02-structural/adapter/Main.java @@ -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 ==="); + } +} diff --git a/02-structural/adapter/OrderService.java b/02-structural/adapter/OrderService.java new file mode 100644 index 0000000..640371d --- /dev/null +++ b/02-structural/adapter/OrderService.java @@ -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."); + } + } +} diff --git a/02-structural/adapter/PaymentGateway.java b/02-structural/adapter/PaymentGateway.java new file mode 100644 index 0000000..29223bc --- /dev/null +++ b/02-structural/adapter/PaymentGateway.java @@ -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); +} diff --git a/02-structural/adapter/README.md b/02-structural/adapter/README.md new file mode 100644 index 0000000..0ea8249 --- /dev/null +++ b/02-structural/adapter/README.md @@ -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/ diff --git a/02-structural/adapter/StripeClient.java b/02-structural/adapter/StripeClient.java new file mode 100644 index 0000000..3db71da --- /dev/null +++ b/02-structural/adapter/StripeClient.java @@ -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; + } + } +} diff --git a/02-structural/adapter/StripePaymentAdapter.java b/02-structural/adapter/StripePaymentAdapter.java new file mode 100644 index 0000000..7f33ca6 --- /dev/null +++ b/02-structural/adapter/StripePaymentAdapter.java @@ -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"; + } +} diff --git a/02-structural/bridge/AdvancedRemote.java b/02-structural/bridge/AdvancedRemote.java new file mode 100644 index 0000000..452ea75 --- /dev/null +++ b/02-structural/bridge/AdvancedRemote.java @@ -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); + } +} diff --git a/02-structural/bridge/Device.java b/02-structural/bridge/Device.java new file mode 100644 index 0000000..47ab925 --- /dev/null +++ b/02-structural/bridge/Device.java @@ -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(); +} diff --git a/02-structural/bridge/Main.java b/02-structural/bridge/Main.java new file mode 100644 index 0000000..0952884 --- /dev/null +++ b/02-structural/bridge/Main.java @@ -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."); + } +} diff --git a/02-structural/bridge/README.md b/02-structural/bridge/README.md new file mode 100644 index 0000000..c13c174 --- /dev/null +++ b/02-structural/bridge/README.md @@ -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/ diff --git a/02-structural/bridge/Radio.java b/02-structural/bridge/Radio.java new file mode 100644 index 0000000..9f67371 --- /dev/null +++ b/02-structural/bridge/Radio.java @@ -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"; } +} diff --git a/02-structural/bridge/RemoteControl.java b/02-structural/bridge/RemoteControl.java new file mode 100644 index 0000000..238aed8 --- /dev/null +++ b/02-structural/bridge/RemoteControl.java @@ -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); } +} diff --git a/02-structural/bridge/TV.java b/02-structural/bridge/TV.java new file mode 100644 index 0000000..c956e4d --- /dev/null +++ b/02-structural/bridge/TV.java @@ -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"; } +} diff --git a/02-structural/composite/Directory.java b/02-structural/composite/Directory.java new file mode 100644 index 0000000..5e638b5 --- /dev/null +++ b/02-structural/composite/Directory.java @@ -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 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 + } + } +} diff --git a/02-structural/composite/File.java b/02-structural/composite/File.java new file mode 100644 index 0000000..6ae4734 --- /dev/null +++ b/02-structural/composite/File.java @@ -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); + } +} diff --git a/02-structural/composite/FileSystemItem.java b/02-structural/composite/FileSystemItem.java new file mode 100644 index 0000000..be0d5d6 --- /dev/null +++ b/02-structural/composite/FileSystemItem.java @@ -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 +} diff --git a/02-structural/composite/Main.java b/02-structural/composite/Main.java new file mode 100644 index 0000000..fe19845 --- /dev/null +++ b/02-structural/composite/Main.java @@ -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 ==="); + } +} diff --git a/02-structural/decorator/Main.java b/02-structural/decorator/Main.java new file mode 100644 index 0000000..7139696 --- /dev/null +++ b/02-structural/decorator/Main.java @@ -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 ==="); + } +} diff --git a/02-structural/decorator/PlainTextProcessor.java b/02-structural/decorator/PlainTextProcessor.java new file mode 100644 index 0000000..18bc4fd --- /dev/null +++ b/02-structural/decorator/PlainTextProcessor.java @@ -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 + } +} diff --git a/02-structural/decorator/ProfanityFilterDecorator.java b/02-structural/decorator/ProfanityFilterDecorator.java new file mode 100644 index 0000000..9b4b259 --- /dev/null +++ b/02-structural/decorator/ProfanityFilterDecorator.java @@ -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; + } +} diff --git a/02-structural/decorator/TextDecorator.java b/02-structural/decorator/TextDecorator.java new file mode 100644 index 0000000..303b9f2 --- /dev/null +++ b/02-structural/decorator/TextDecorator.java @@ -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); + } +} diff --git a/02-structural/decorator/TextProcessor.java b/02-structural/decorator/TextProcessor.java new file mode 100644 index 0000000..db7507f --- /dev/null +++ b/02-structural/decorator/TextProcessor.java @@ -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); +} diff --git a/02-structural/decorator/TrimDecorator.java b/02-structural/decorator/TrimDecorator.java new file mode 100644 index 0000000..73bbbe5 --- /dev/null +++ b/02-structural/decorator/TrimDecorator.java @@ -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(); + } +} diff --git a/02-structural/decorator/UpperCaseDecorator.java b/02-structural/decorator/UpperCaseDecorator.java new file mode 100644 index 0000000..625dbeb --- /dev/null +++ b/02-structural/decorator/UpperCaseDecorator.java @@ -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(); + } +} diff --git a/02-structural/facade/Main.java b/02-structural/facade/Main.java new file mode 100644 index 0000000..d72de06 --- /dev/null +++ b/02-structural/facade/Main.java @@ -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 ==="); + } +} diff --git a/02-structural/facade/Subsystems.java b/02-structural/facade/Subsystems.java new file mode 100644 index 0000000..7072e5c --- /dev/null +++ b/02-structural/facade/Subsystems.java @@ -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()); + } +} diff --git a/02-structural/facade/VideoConversionFacade.java b/02-structural/facade/VideoConversionFacade.java new file mode 100644 index 0000000..65c0daf --- /dev/null +++ b/02-structural/facade/VideoConversionFacade.java @@ -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; + } +} diff --git a/02-structural/flyweight/Main.java b/02-structural/flyweight/Main.java new file mode 100644 index 0000000..6442ade --- /dev/null +++ b/02-structural/flyweight/Main.java @@ -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 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 ==="); + } +} diff --git a/02-structural/flyweight/Tree.java b/02-structural/flyweight/Tree.java new file mode 100644 index 0000000..09cf6f8 --- /dev/null +++ b/02-structural/flyweight/Tree.java @@ -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); + } +} diff --git a/02-structural/flyweight/TreeFactory.java b/02-structural/flyweight/TreeFactory.java new file mode 100644 index 0000000..3b14530 --- /dev/null +++ b/02-structural/flyweight/TreeFactory.java @@ -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 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(); + } +} diff --git a/02-structural/flyweight/TreeType.java b/02-structural/flyweight/TreeType.java new file mode 100644 index 0000000..fd4f16f --- /dev/null +++ b/02-structural/flyweight/TreeType.java @@ -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); + } +} diff --git a/02-structural/proxy/DatabaseConnection.java b/02-structural/proxy/DatabaseConnection.java new file mode 100644 index 0000000..f95f421 --- /dev/null +++ b/02-structural/proxy/DatabaseConnection.java @@ -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(); +} diff --git a/02-structural/proxy/LazyConnectionProxy.java b/02-structural/proxy/LazyConnectionProxy.java new file mode 100644 index 0000000..9327a61 --- /dev/null +++ b/02-structural/proxy/LazyConnectionProxy.java @@ -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"); + } + } +} diff --git a/02-structural/proxy/LoggingProxy.java b/02-structural/proxy/LoggingProxy.java new file mode 100644 index 0000000..ae0111d --- /dev/null +++ b/02-structural/proxy/LoggingProxy.java @@ -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(); + } +} diff --git a/02-structural/proxy/Main.java b/02-structural/proxy/Main.java new file mode 100644 index 0000000..cbd3978 --- /dev/null +++ b/02-structural/proxy/Main.java @@ -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 ==="); + } +} diff --git a/02-structural/proxy/RealDatabaseConnection.java b/02-structural/proxy/RealDatabaseConnection.java new file mode 100644 index 0000000..ae2c75a --- /dev/null +++ b/02-structural/proxy/RealDatabaseConnection.java @@ -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); + } +} diff --git a/03-behavioral/chain-of-responsibility/CriticalIncidentTeam.java b/03-behavioral/chain-of-responsibility/CriticalIncidentTeam.java new file mode 100644 index 0000000..d1b0d0c --- /dev/null +++ b/03-behavioral/chain-of-responsibility/CriticalIncidentTeam.java @@ -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); + } +} diff --git a/03-behavioral/chain-of-responsibility/Level1Support.java b/03-behavioral/chain-of-responsibility/Level1Support.java new file mode 100644 index 0000000..0185473 --- /dev/null +++ b/03-behavioral/chain-of-responsibility/Level1Support.java @@ -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); + } +} diff --git a/03-behavioral/chain-of-responsibility/Level2Support.java b/03-behavioral/chain-of-responsibility/Level2Support.java new file mode 100644 index 0000000..2172501 --- /dev/null +++ b/03-behavioral/chain-of-responsibility/Level2Support.java @@ -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); + } +} diff --git a/03-behavioral/chain-of-responsibility/Level3Support.java b/03-behavioral/chain-of-responsibility/Level3Support.java new file mode 100644 index 0000000..ccf1898 --- /dev/null +++ b/03-behavioral/chain-of-responsibility/Level3Support.java @@ -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); + } +} diff --git a/03-behavioral/chain-of-responsibility/Main.java b/03-behavioral/chain-of-responsibility/Main.java new file mode 100644 index 0000000..3330d53 --- /dev/null +++ b/03-behavioral/chain-of-responsibility/Main.java @@ -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 ==="); + } +} diff --git a/03-behavioral/chain-of-responsibility/SupportHandler.java b/03-behavioral/chain-of-responsibility/SupportHandler.java new file mode 100644 index 0000000..edd13f6 --- /dev/null +++ b/03-behavioral/chain-of-responsibility/SupportHandler.java @@ -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); +} diff --git a/03-behavioral/chain-of-responsibility/SupportTicket.java b/03-behavioral/chain-of-responsibility/SupportTicket.java new file mode 100644 index 0000000..5bce18a --- /dev/null +++ b/03-behavioral/chain-of-responsibility/SupportTicket.java @@ -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; + } +} diff --git a/03-behavioral/command/Command.java b/03-behavioral/command/Command.java new file mode 100644 index 0000000..3345d32 --- /dev/null +++ b/03-behavioral/command/Command.java @@ -0,0 +1,8 @@ +package command; + +/** Command interface: every action is an object */ +public interface Command { + void execute(); + void undo(); + String getDescription(); +} diff --git a/03-behavioral/command/CommandHistory.java b/03-behavioral/command/CommandHistory.java new file mode 100644 index 0000000..98abde2 --- /dev/null +++ b/03-behavioral/command/CommandHistory.java @@ -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 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()); + } +} diff --git a/03-behavioral/command/DeleteCommand.java b/03-behavioral/command/DeleteCommand.java new file mode 100644 index 0000000..e18b86a --- /dev/null +++ b/03-behavioral/command/DeleteCommand.java @@ -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; } +} diff --git a/03-behavioral/command/InsertCommand.java b/03-behavioral/command/InsertCommand.java new file mode 100644 index 0000000..88e63ae --- /dev/null +++ b/03-behavioral/command/InsertCommand.java @@ -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; } +} diff --git a/03-behavioral/command/Main.java b/03-behavioral/command/Main.java new file mode 100644 index 0000000..951bca1 --- /dev/null +++ b/03-behavioral/command/Main.java @@ -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 ==="); + } +} diff --git a/03-behavioral/command/TextEditor.java b/03-behavioral/command/TextEditor.java new file mode 100644 index 0000000..19a1927 --- /dev/null +++ b/03-behavioral/command/TextEditor.java @@ -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 + "\"]"; } +} diff --git a/03-behavioral/interpreter/AddExpression.java b/03-behavioral/interpreter/AddExpression.java new file mode 100644 index 0000000..ee6df07 --- /dev/null +++ b/03-behavioral/interpreter/AddExpression.java @@ -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(); + } +} diff --git a/03-behavioral/interpreter/Expression.java b/03-behavioral/interpreter/Expression.java new file mode 100644 index 0000000..7500056 --- /dev/null +++ b/03-behavioral/interpreter/Expression.java @@ -0,0 +1,9 @@ +package interpreter; + +/** + * AbstractExpression — declares the interpret operation. + * All terminal and non-terminal expressions implement this. + */ +public interface Expression { + int interpret(); +} diff --git a/03-behavioral/interpreter/Main.java b/03-behavioral/interpreter/Main.java new file mode 100644 index 0000000..aa14053 --- /dev/null +++ b/03-behavioral/interpreter/Main.java @@ -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()); + } +} diff --git a/03-behavioral/interpreter/MultiplyExpression.java b/03-behavioral/interpreter/MultiplyExpression.java new file mode 100644 index 0000000..6fb4102 --- /dev/null +++ b/03-behavioral/interpreter/MultiplyExpression.java @@ -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(); + } +} diff --git a/03-behavioral/interpreter/NumberExpression.java b/03-behavioral/interpreter/NumberExpression.java new file mode 100644 index 0000000..c0367b7 --- /dev/null +++ b/03-behavioral/interpreter/NumberExpression.java @@ -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; + } +} diff --git a/03-behavioral/interpreter/SubtractExpression.java b/03-behavioral/interpreter/SubtractExpression.java new file mode 100644 index 0000000..00ff14c --- /dev/null +++ b/03-behavioral/interpreter/SubtractExpression.java @@ -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(); + } +} diff --git a/03-behavioral/iterator/Book.java b/03-behavioral/iterator/Book.java new file mode 100644 index 0000000..ea51a39 --- /dev/null +++ b/03-behavioral/iterator/Book.java @@ -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 + ")"; } +} diff --git a/03-behavioral/iterator/BookIterator.java b/03-behavioral/iterator/BookIterator.java new file mode 100644 index 0000000..9e31514 --- /dev/null +++ b/03-behavioral/iterator/BookIterator.java @@ -0,0 +1,7 @@ +package iterator; + +/** Our custom Iterator interface (mirrors java.util.Iterator) */ +public interface BookIterator { + boolean hasNext(); + Book next(); +} diff --git a/03-behavioral/iterator/BookShelf.java b/03-behavioral/iterator/BookShelf.java new file mode 100644 index 0000000..14bf2d6 --- /dev/null +++ b/03-behavioral/iterator/BookShelf.java @@ -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 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; + } + } +} diff --git a/03-behavioral/iterator/Main.java b/03-behavioral/iterator/Main.java new file mode 100644 index 0000000..5e73732 --- /dev/null +++ b/03-behavioral/iterator/Main.java @@ -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 ==="); + } +} diff --git a/03-behavioral/mediator/ChatMediator.java b/03-behavioral/mediator/ChatMediator.java new file mode 100644 index 0000000..2564788 --- /dev/null +++ b/03-behavioral/mediator/ChatMediator.java @@ -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); +} diff --git a/03-behavioral/mediator/ChatRoom.java b/03-behavioral/mediator/ChatRoom.java new file mode 100644 index 0000000..3a9a578 --- /dev/null +++ b/03-behavioral/mediator/ChatRoom.java @@ -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 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()); + } + } + } +} diff --git a/03-behavioral/mediator/Main.java b/03-behavioral/mediator/Main.java new file mode 100644 index 0000000..51d0201 --- /dev/null +++ b/03-behavioral/mediator/Main.java @@ -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 ==="); + } +} diff --git a/03-behavioral/mediator/User.java b/03-behavioral/mediator/User.java new file mode 100644 index 0000000..166eb9a --- /dev/null +++ b/03-behavioral/mediator/User.java @@ -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); + } +} diff --git a/03-behavioral/memento/Editor.java b/03-behavioral/memento/Editor.java new file mode 100644 index 0000000..85671d1 --- /dev/null +++ b/03-behavioral/memento/Editor.java @@ -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 + "]"; + } +} diff --git a/03-behavioral/memento/EditorMemento.java b/03-behavioral/memento/EditorMemento.java new file mode 100644 index 0000000..f07fe01 --- /dev/null +++ b/03-behavioral/memento/EditorMemento.java @@ -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 + "]"; + } +} diff --git a/03-behavioral/memento/History.java b/03-behavioral/memento/History.java new file mode 100644 index 0000000..9551091 --- /dev/null +++ b/03-behavioral/memento/History.java @@ -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 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(); } +} diff --git a/03-behavioral/memento/Main.java b/03-behavioral/memento/Main.java new file mode 100644 index 0000000..22c62be --- /dev/null +++ b/03-behavioral/memento/Main.java @@ -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 ==="); + } +} diff --git a/03-behavioral/observer/AlertObserver.java b/03-behavioral/observer/AlertObserver.java new file mode 100644 index 0000000..6c5cd35 --- /dev/null +++ b/03-behavioral/observer/AlertObserver.java @@ -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); + } + } +} diff --git a/03-behavioral/observer/Main.java b/03-behavioral/observer/Main.java new file mode 100644 index 0000000..865398d --- /dev/null +++ b/03-behavioral/observer/Main.java @@ -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 ==="); + } +} diff --git a/03-behavioral/observer/PortfolioObserver.java b/03-behavioral/observer/PortfolioObserver.java new file mode 100644 index 0000000..3aca994 --- /dev/null +++ b/03-behavioral/observer/PortfolioObserver.java @@ -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); + } +} diff --git a/03-behavioral/observer/StockMarket.java b/03-behavioral/observer/StockMarket.java new file mode 100644 index 0000000..33ddf68 --- /dev/null +++ b/03-behavioral/observer/StockMarket.java @@ -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 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); + } + } +} diff --git a/03-behavioral/observer/StockObserver.java b/03-behavioral/observer/StockObserver.java new file mode 100644 index 0000000..4403d8d --- /dev/null +++ b/03-behavioral/observer/StockObserver.java @@ -0,0 +1,6 @@ +package observer; + +/** Observer interface — all subscribers implement this */ +public interface StockObserver { + void onPriceChanged(String ticker, double oldPrice, double newPrice); +} diff --git a/03-behavioral/state/GreenState.java b/03-behavioral/state/GreenState.java new file mode 100644 index 0000000..acb5277 --- /dev/null +++ b/03-behavioral/state/GreenState.java @@ -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"; } +} diff --git a/03-behavioral/state/Main.java b/03-behavioral/state/Main.java new file mode 100644 index 0000000..d3b53ae --- /dev/null +++ b/03-behavioral/state/Main.java @@ -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 ==="); + } +} diff --git a/03-behavioral/state/RedState.java b/03-behavioral/state/RedState.java new file mode 100644 index 0000000..83c82a8 --- /dev/null +++ b/03-behavioral/state/RedState.java @@ -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"; } +} diff --git a/03-behavioral/state/TrafficLight.java b/03-behavioral/state/TrafficLight.java new file mode 100644 index 0000000..9b46c9d --- /dev/null +++ b/03-behavioral/state/TrafficLight.java @@ -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(); } +} diff --git a/03-behavioral/state/TrafficLightState.java b/03-behavioral/state/TrafficLightState.java new file mode 100644 index 0000000..6749c02 --- /dev/null +++ b/03-behavioral/state/TrafficLightState.java @@ -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(); +} diff --git a/03-behavioral/state/YellowState.java b/03-behavioral/state/YellowState.java new file mode 100644 index 0000000..b772d37 --- /dev/null +++ b/03-behavioral/state/YellowState.java @@ -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"; } +} diff --git a/03-behavioral/strategy/BubbleSort.java b/03-behavioral/strategy/BubbleSort.java new file mode 100644 index 0000000..991d14c --- /dev/null +++ b/03-behavioral/strategy/BubbleSort.java @@ -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"; } +} diff --git a/03-behavioral/strategy/Main.java b/03-behavioral/strategy/Main.java new file mode 100644 index 0000000..9d0f071 --- /dev/null +++ b/03-behavioral/strategy/Main.java @@ -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 ==="); + } +} diff --git a/03-behavioral/strategy/MergeSort.java b/03-behavioral/strategy/MergeSort.java new file mode 100644 index 0000000..4be72ad --- /dev/null +++ b/03-behavioral/strategy/MergeSort.java @@ -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"; } +} diff --git a/03-behavioral/strategy/QuickSort.java b/03-behavioral/strategy/QuickSort.java new file mode 100644 index 0000000..e5cb201 --- /dev/null +++ b/03-behavioral/strategy/QuickSort.java @@ -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"; } +} diff --git a/03-behavioral/strategy/SortStrategy.java b/03-behavioral/strategy/SortStrategy.java new file mode 100644 index 0000000..88236f1 --- /dev/null +++ b/03-behavioral/strategy/SortStrategy.java @@ -0,0 +1,7 @@ +package strategy; + +/** Strategy interface — all sorting algorithms implement this */ +public interface SortStrategy { + void sort(int[] data); + String getName(); +} diff --git a/03-behavioral/strategy/Sorter.java b/03-behavioral/strategy/Sorter.java new file mode 100644 index 0000000..649d43d --- /dev/null +++ b/03-behavioral/strategy/Sorter.java @@ -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; + } +} diff --git a/03-behavioral/template-method/ApiMigration.java b/03-behavioral/template-method/ApiMigration.java new file mode 100644 index 0000000..609366b --- /dev/null +++ b/03-behavioral/template-method/ApiMigration.java @@ -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; } +} diff --git a/03-behavioral/template-method/CsvMigration.java b/03-behavioral/template-method/CsvMigration.java new file mode 100644 index 0000000..705caf9 --- /dev/null +++ b/03-behavioral/template-method/CsvMigration.java @@ -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 + } +} diff --git a/03-behavioral/template-method/DataMigration.java b/03-behavioral/template-method/DataMigration.java new file mode 100644 index 0000000..92eb734 --- /dev/null +++ b/03-behavioral/template-method/DataMigration.java @@ -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."); + } +} diff --git a/03-behavioral/template-method/Main.java b/03-behavioral/template-method/Main.java new file mode 100644 index 0000000..7386f47 --- /dev/null +++ b/03-behavioral/template-method/Main.java @@ -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 ==="); + } +} diff --git a/03-behavioral/visitor/AreaCalculator.java b/03-behavioral/visitor/AreaCalculator.java new file mode 100644 index 0000000..991e15f --- /dev/null +++ b/03-behavioral/visitor/AreaCalculator.java @@ -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); + } +} diff --git a/03-behavioral/visitor/Circle.java b/03-behavioral/visitor/Circle.java new file mode 100644 index 0000000..db187bf --- /dev/null +++ b/03-behavioral/visitor/Circle.java @@ -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); } +} diff --git a/03-behavioral/visitor/Main.java b/03-behavioral/visitor/Main.java new file mode 100644 index 0000000..004f8e9 --- /dev/null +++ b/03-behavioral/visitor/Main.java @@ -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 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 ==="); + } +} diff --git a/03-behavioral/visitor/PerimeterCalculator.java b/03-behavioral/visitor/PerimeterCalculator.java new file mode 100644 index 0000000..6d6ee17 --- /dev/null +++ b/03-behavioral/visitor/PerimeterCalculator.java @@ -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); + } +} diff --git a/03-behavioral/visitor/Rectangle.java b/03-behavioral/visitor/Rectangle.java new file mode 100644 index 0000000..8c21417 --- /dev/null +++ b/03-behavioral/visitor/Rectangle.java @@ -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); } +} diff --git a/03-behavioral/visitor/Shape.java b/03-behavioral/visitor/Shape.java new file mode 100644 index 0000000..af3340e --- /dev/null +++ b/03-behavioral/visitor/Shape.java @@ -0,0 +1,7 @@ +package visitor; + +/** Element interface — accepts a visitor */ +public interface Shape { + void accept(ShapeVisitor visitor); + String getName(); +} diff --git a/03-behavioral/visitor/ShapeVisitor.java b/03-behavioral/visitor/ShapeVisitor.java new file mode 100644 index 0000000..ac781fd --- /dev/null +++ b/03-behavioral/visitor/ShapeVisitor.java @@ -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); +} diff --git a/03-behavioral/visitor/Triangle.java b/03-behavioral/visitor/Triangle.java new file mode 100644 index 0000000..3139c75 --- /dev/null +++ b/03-behavioral/visitor/Triangle.java @@ -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); } +} diff --git a/PUSH-TO-GITEA.bat b/PUSH-TO-GITEA.bat new file mode 100644 index 0000000..0dd548c --- /dev/null +++ b/PUSH-TO-GITEA.bat @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy Bypass -NoExit -File "%~dp0push-to-gitea.ps1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..b587967 --- /dev/null +++ b/README.md @@ -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 diff --git a/RUN-behavioral.bat b/RUN-behavioral.bat new file mode 100644 index 0000000..1493ba2 --- /dev/null +++ b/RUN-behavioral.bat @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy Bypass -NoExit -Command "& 'C:\Users\Ankur\Claude\Projects\@ankurm Blog\design-patterns\run-behavioral.ps1'" diff --git a/RUN.bat b/RUN.bat new file mode 100644 index 0000000..aa75621 --- /dev/null +++ b/RUN.bat @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy Bypass -NoExit -Command "& 'C:\Users\Ankur\Claude\Projects\@ankurm Blog\design-patterns\run-structural.ps1'" diff --git a/compile-and-run-structural.bat b/compile-and-run-structural.bat new file mode 100644 index 0000000..bea0a85 --- /dev/null +++ b/compile-and-run-structural.bat @@ -0,0 +1,74 @@ +@echo off +setlocal + +set BASE=%~dp0 +set OUT=%BASE%\out + +echo ============================================================ +echo Compiling and running structural design pattern examples +echo ============================================================ + +:: Create output dir +if not exist "%OUT%" mkdir "%OUT%" + +:: --- Adapter --- +echo. +echo [1/7] ADAPTER +javac -d "%OUT%\adapter" -sourcepath "%BASE%\02-structural\adapter" "%BASE%\02-structural\adapter\*.java" 2>&1 +if errorlevel 1 (echo COMPILE ERROR - Adapter) else ( + java -cp "%OUT%\adapter" adapter.Main +) + +:: --- Bridge --- +echo. +echo [2/7] BRIDGE +javac -d "%OUT%\bridge" -sourcepath "%BASE%\02-structural\bridge" "%BASE%\02-structural\bridge\*.java" 2>&1 +if errorlevel 1 (echo COMPILE ERROR - Bridge) else ( + java -cp "%OUT%\bridge" bridge.Main +) + +:: --- Composite --- +echo. +echo [3/7] COMPOSITE +javac -d "%OUT%\composite" -sourcepath "%BASE%\02-structural\composite" "%BASE%\02-structural\composite\*.java" 2>&1 +if errorlevel 1 (echo COMPILE ERROR - Composite) else ( + java -cp "%OUT%\composite" composite.Main +) + +:: --- Decorator --- +echo. +echo [4/7] DECORATOR +javac -d "%OUT%\decorator" -sourcepath "%BASE%\02-structural\decorator" "%BASE%\02-structural\decorator\*.java" 2>&1 +if errorlevel 1 (echo COMPILE ERROR - Decorator) else ( + java -cp "%OUT%\decorator" decorator.Main +) + +:: --- Facade --- +echo. +echo [5/7] FACADE +javac -d "%OUT%\facade" -sourcepath "%BASE%\02-structural\facade" "%BASE%\02-structural\facade\*.java" 2>&1 +if errorlevel 1 (echo COMPILE ERROR - Facade) else ( + java -cp "%OUT%\facade" facade.Main +) + +:: --- Flyweight --- +echo. +echo [6/7] FLYWEIGHT +javac -d "%OUT%\flyweight" -sourcepath "%BASE%\02-structural\flyweight" "%BASE%\02-structural\flyweight\*.java" 2>&1 +if errorlevel 1 (echo COMPILE ERROR - Flyweight) else ( + java -cp "%OUT%\flyweight" flyweight.Main +) + +:: --- Proxy --- +echo. +echo [7/7] PROXY +javac -d "%OUT%\proxy" -sourcepath "%BASE%\02-structural\proxy" "%BASE%\02-structural\proxy\*.java" 2>&1 +if errorlevel 1 (echo COMPILE ERROR - Proxy) else ( + java -cp "%OUT%\proxy" proxy.Main +) + +echo. +echo ============================================================ +echo All structural patterns compiled and executed +echo ============================================================ +pause diff --git a/push-to-gitea.ps1 b/push-to-gitea.ps1 new file mode 100644 index 0000000..27d4301 --- /dev/null +++ b/push-to-gitea.ps1 @@ -0,0 +1,50 @@ +$REMOTE = "https://ankurm.com/git.app/asmhatre/design-patterns.git" +$BRANCH = "main" + +Set-Location $PSScriptRoot + +# Always run git init - it is safe and idempotent on an existing repo +git init + +# Configure remote +$existing = git remote get-url origin 2>$null +if ($LASTEXITCODE -ne 0) { + git remote add origin $REMOTE + Write-Host "Added remote." -ForegroundColor Green +} elseif ($existing.Trim() -ne $REMOTE) { + git remote set-url origin $REMOTE + Write-Host "Updated remote." -ForegroundColor Yellow +} else { + Write-Host "Remote OK: $REMOTE" -ForegroundColor Cyan +} + +# Create .gitignore if missing +if (-not (Test-Path ".gitignore")) { + Set-Content ".gitignore" "out/`n*.class`nscreenshots/*.png`n*.b64.txt" + Write-Host "Created .gitignore" -ForegroundColor Green +} + +# Stage and commit +git add -A +$status = git status --short + +if (-not $status) { + Write-Host "Nothing to commit - repo is up to date." -ForegroundColor Cyan + exit 0 +} + +Write-Host "`nFiles to commit:" -ForegroundColor Yellow +Write-Host $status + +$timestamp = Get-Date -Format "yyyy-MM-dd" +git commit -m "Add all 23 GoF design pattern implementations ($timestamp)" + +Write-Host "`nPushing to $REMOTE ..." -ForegroundColor Yellow +git push -u origin $BRANCH + +if ($LASTEXITCODE -eq 0) { + Write-Host "`nDone! Code live at $REMOTE" -ForegroundColor Green +} else { + Write-Host "`nPush failed. Try manually: git push -u origin main" -ForegroundColor Red + Write-Host "If prompted, enter your Gitea username and password." -ForegroundColor Yellow +} diff --git a/run-all.bat b/run-all.bat new file mode 100644 index 0000000..7d12222 --- /dev/null +++ b/run-all.bat @@ -0,0 +1,80 @@ +@echo off +setlocal EnableDelayedExpansion + +set BASE=%~dp0 +set JAVAC="C:\Program Files\Eclipse Adoptium\jdk-17.0.7.7-hotspot\bin\javac.exe" +set JAVA="C:\Program Files\Eclipse Adoptium\jdk-17.0.7.7-hotspot\bin\java.exe" +set LOG=%BASE%output.log + +echo Design Patterns - Compile and Run > "%LOG%" +echo Java: 17.0.7 (Eclipse Temurin) >> "%LOG%" +echo ================================ >> "%LOG%" + +:: Create output dirs +for %%P in (adapter bridge composite decorator facade flyweight proxy) do ( + if not exist "%BASE%out\%%P" mkdir "%BASE%out\%%P" +) + +:: Function-like subroutine to compile a package +:: Usage: CALL :compile_pkg + +:: --- ADAPTER --- +echo. >> "%LOG%" +echo === ADAPTER === >> "%LOG%" +set FILES= +for %%f in ("%BASE%02-structural\adapter\*.java") do set FILES=!FILES! "%%f" +%JAVAC% -d "%BASE%out\adapter" !FILES! >> "%LOG%" 2>&1 +if not errorlevel 1 (%JAVA% -cp "%BASE%out\adapter" adapter.Main >> "%LOG%" 2>&1) + +:: --- BRIDGE --- +echo. >> "%LOG%" +echo === BRIDGE === >> "%LOG%" +set FILES= +for %%f in ("%BASE%02-structural\bridge\*.java") do set FILES=!FILES! "%%f" +%JAVAC% -d "%BASE%out\bridge" !FILES! >> "%LOG%" 2>&1 +if not errorlevel 1 (%JAVA% -cp "%BASE%out\bridge" bridge.Main >> "%LOG%" 2>&1) + +:: --- COMPOSITE --- +echo. >> "%LOG%" +echo === COMPOSITE === >> "%LOG%" +set FILES= +for %%f in ("%BASE%02-structural\composite\*.java") do set FILES=!FILES! "%%f" +%JAVAC% -d "%BASE%out\composite" !FILES! >> "%LOG%" 2>&1 +if not errorlevel 1 (%JAVA% -cp "%BASE%out\composite" composite.Main >> "%LOG%" 2>&1) + +:: --- DECORATOR --- +echo. >> "%LOG%" +echo === DECORATOR === >> "%LOG%" +set FILES= +for %%f in ("%BASE%02-structural\decorator\*.java") do set FILES=!FILES! "%%f" +%JAVAC% -d "%BASE%out\decorator" !FILES! >> "%LOG%" 2>&1 +if not errorlevel 1 (%JAVA% -cp "%BASE%out\decorator" decorator.Main >> "%LOG%" 2>&1) + +:: --- FACADE --- +echo. >> "%LOG%" +echo === FACADE === >> "%LOG%" +set FILES= +for %%f in ("%BASE%02-structural\facade\*.java") do set FILES=!FILES! "%%f" +%JAVAC% -d "%BASE%out\facade" !FILES! >> "%LOG%" 2>&1 +if not errorlevel 1 (%JAVA% -cp "%BASE%out\facade" facade.Main >> "%LOG%" 2>&1) + +:: --- FLYWEIGHT --- +echo. >> "%LOG%" +echo === FLYWEIGHT === >> "%LOG%" +set FILES= +for %%f in ("%BASE%02-structural\flyweight\*.java") do set FILES=!FILES! "%%f" +%JAVAC% -d "%BASE%out\flyweight" !FILES! >> "%LOG%" 2>&1 +if not errorlevel 1 (%JAVA% -cp "%BASE%out\flyweight" flyweight.Main >> "%LOG%" 2>&1) + +:: --- PROXY --- +echo. >> "%LOG%" +echo === PROXY === >> "%LOG%" +set FILES= +for %%f in ("%BASE%02-structural\proxy\*.java") do set FILES=!FILES! "%%f" +%JAVAC% -d "%BASE%out\proxy" !FILES! >> "%LOG%" 2>&1 +if not errorlevel 1 (%JAVA% -cp "%BASE%out\proxy" proxy.Main >> "%LOG%" 2>&1) + +echo. >> "%LOG%" +echo [DONE] >> "%LOG%" + +notepad "%LOG%" diff --git a/run-behavioral.ps1 b/run-behavioral.ps1 new file mode 100644 index 0000000..fa6cecb --- /dev/null +++ b/run-behavioral.ps1 @@ -0,0 +1,62 @@ +$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' +$BASE = 'C:\Users\Ankur\Claude\Projects\@ankurm Blog\design-patterns' +$OUTDIR = "$BASE\screenshots" + +New-Item -Force -ItemType Directory $OUTDIR | Out-Null + +$patterns = @( + @{dir="chain-of-responsibility"; pkg="chain"; main="chain.Main"}, + @{dir="command"; pkg="command"; main="command.Main"}, + @{dir="iterator"; pkg="iterator"; main="iterator.Main"}, + @{dir="mediator"; pkg="mediator"; main="mediator.Main"}, + @{dir="memento"; pkg="memento"; main="memento.Main"}, + @{dir="observer"; pkg="observer"; main="observer.Main"}, + @{dir="state"; pkg="state"; main="state.Main"}, + @{dir="strategy"; pkg="strategy"; main="strategy.Main"}, + @{dir="template-method"; pkg="template"; main="template.Main"}, + @{dir="visitor"; pkg="visitor"; main="visitor.Main"} +) + +$allOk = $true + +foreach ($p in $patterns) { + $label = $p.dir.ToUpper() + Write-Host "`n=== $label ===" -ForegroundColor Cyan + + $srcDir = "$BASE\03-behavioral\$($p.dir)" + $outDir = "$BASE\out\$($p.pkg)" + New-Item -Force -ItemType Directory $outDir | Out-Null + + $files = @(Get-ChildItem "$srcDir\*.java" | Select-Object -ExpandProperty FullName) + + if ($files.Count -eq 0) { + Write-Host "[SKIP] No .java files in $srcDir" -ForegroundColor Yellow + continue + } + + $compileOut = & $JAVAC -d $outDir @files 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host "[FAIL] $label compile error:" -ForegroundColor Red + $compileOut | ForEach-Object { Write-Host " $_" -ForegroundColor Red } + $allOk = $false + continue + } + + Write-Host "[OK] Compiled $($p.dir)" -ForegroundColor Green + + $logFile = "$OUTDIR\$($p.pkg)-output.txt" + $runOut = & $JAVA -cp $outDir $($p.main) 2>&1 + $runOut | Out-File -FilePath $logFile -Encoding utf8 + $runOut | ForEach-Object { Write-Host $_ } + Write-Host "[SAVED] $logFile" -ForegroundColor DarkGray +} + +Write-Host "`n============================================" -ForegroundColor Yellow +if ($allOk) { + Write-Host "All 10 behavioral patterns compiled and ran OK" -ForegroundColor Green +} else { + Write-Host "Some patterns had errors - check output above" -ForegroundColor Red +} +Write-Host "Output files: $OUTDIR" -ForegroundColor Yellow +Read-Host "`nPress Enter to close" diff --git a/run-structural.ps1 b/run-structural.ps1 new file mode 100644 index 0000000..ea7e662 --- /dev/null +++ b/run-structural.ps1 @@ -0,0 +1,61 @@ +$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' +$BASE = 'C:\Users\Ankur\Claude\Projects\@ankurm Blog\design-patterns' +$OUTDIR = "$BASE\screenshots" + +New-Item -Force -ItemType Directory $OUTDIR | Out-Null + +$patterns = @( + @{dir="adapter"; main="adapter.Main"}, + @{dir="bridge"; main="bridge.Main"}, + @{dir="composite"; main="composite.Main"}, + @{dir="decorator"; main="decorator.Main"}, + @{dir="facade"; main="facade.Main"}, + @{dir="flyweight"; main="flyweight.Main"}, + @{dir="proxy"; main="proxy.Main"} +) + +$allOk = $true + +foreach ($p in $patterns) { + $label = $p.dir.ToUpper() + Write-Host "`n=== $label ===" -ForegroundColor Cyan + + $srcDir = "$BASE\02-structural\$($p.dir)" + $outDir = "$BASE\out\$($p.dir)" + New-Item -Force -ItemType Directory $outDir | Out-Null + + $files = @(Get-ChildItem "$srcDir\*.java" | Select-Object -ExpandProperty FullName) + + if ($files.Count -eq 0) { + Write-Host "[SKIP] No .java files in $srcDir" -ForegroundColor Yellow + continue + } + + # Compile + $compileOut = & $JAVAC -d $outDir @files 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host "[FAIL] $label compile error:" -ForegroundColor Red + $compileOut | ForEach-Object { Write-Host " $_" -ForegroundColor Red } + $allOk = $false + continue + } + + Write-Host "[OK] Compiled $($p.dir)" -ForegroundColor Green + + # Run and save to per-pattern file + $logFile = "$OUTDIR\$($p.dir)-output.txt" + $runOut = & $JAVA -cp $outDir $($p.main) 2>&1 + $runOut | Out-File -FilePath $logFile -Encoding utf8 + $runOut | ForEach-Object { Write-Host $_ } + Write-Host "[SAVED] $logFile" -ForegroundColor DarkGray +} + +Write-Host "`n============================================" -ForegroundColor Yellow +if ($allOk) { + Write-Host "All 7 structural patterns compiled and ran OK" -ForegroundColor Green +} else { + Write-Host "Some patterns had errors - check output above" -ForegroundColor Red +} +Write-Host "Output files: $OUTDIR" -ForegroundColor Yellow +Read-Host "`nPress Enter to close"