Add all 23 GoF design pattern implementations

This commit is contained in:
2026-07-25 10:50:29 +05:30
commit f5688a6b32
164 changed files with 4371 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
package adapter;
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 --");
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 ===");
}
}

View File

@@ -0,0 +1,23 @@
package adapter;
/**
* The Client — uses only the PaymentGateway interface.
* It has no idea whether it's talking to Stripe, PayPal, or Braintree.
*/
public class OrderService {
private final PaymentGateway gateway;
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.");
}
}
}

View File

@@ -0,0 +1,10 @@
package adapter;
/**
* The Target interface — what YOUR application's code expects.
* Every payment gateway in your system must implement this.
*/
public interface PaymentGateway {
boolean charge(String customerId, double amount, String currency);
boolean refund(String transactionId, double amount);
String getStatus(String transactionId);
}

View File

@@ -0,0 +1,36 @@
# 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 25.
## Files
| Post Section | File(s) |
|---|---|
| Step 1 — Define the Target Interface | `PaymentGateway.java` |
| Step 2 — The Adaptee (What You're Wrapping) | `StripeClient.java` |
| Step 3 — The Object Adapter | `StripePaymentAdapter.java` |
| Step 4 — The Client (Knows Nothing About Stripe) | `OrderService.java` |
| Putting It Together: The Main Demo | `Main.java` |
| Object Adapter vs Class Adapter | `StripePaymentClassAdapter.java` |
Note: the `InputStreamReader` JDK-adapter snippet is illustrative only — it is not part of this repository's runnable example.
## See Also
- Full article: https://ankurm.com/adapter-design-pattern-java/
- All design patterns: https://ankurm.com/design-patterns-java/

View File

@@ -0,0 +1,39 @@
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.
*/
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);
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");
}
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;
}
}
}

View File

@@ -0,0 +1,36 @@
package adapter;
/**
* The Adapter — bridges StripeClient to PaymentGateway.
*
* Object Adapter variant: holds a StripeClient instance via composition.
* This means it can wrap any StripeClient including subclasses.
*/
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 1: dollars → cents
long amountInCents = Math.round(amount * 100);
// Translation 2: charge() → createCharge(), lowercase currency
StripeClient.StripeChargeResult result =
stripe.createCharge(customerId, amountInCents, currency.toLowerCase());
// Translation 3: StripeChargeResult → boolean
return result.success;
}
@Override
public boolean refund(String transactionId, double amount) {
long amountInCents = Math.round(amount * 100);
// Translation: refund() → issueRefund(), your "transactionId" is Stripe's "chargeId"
return stripe.issueRefund(transactionId, amountInCents);
}
@Override
public String getStatus(String transactionId) {
// Translation: getStatus() → retrieveCharge(), StripeChargeResult → String
StripeClient.StripeChargeResult result = stripe.retrieveCharge(transactionId);
if (!result.success) return "FAILED";
return result.status != null ? result.status.toUpperCase() : "UNKNOWN";
}
}

View File

@@ -0,0 +1,23 @@
package adapter;
// Class Adapter: extends Adaptee, implements Target
// Can only be used when you CAN extend the Adaptee (it's not final)
public class StripePaymentClassAdapter extends StripeClient implements PaymentGateway {
public StripePaymentClassAdapter(String apiKey) {
super(apiKey);
}
@Override
public boolean charge(String customerId, double amount, String currency) {
long cents = Math.round(amount * 100);
return createCharge(customerId, cents, currency.toLowerCase()).success;
// Note: calls inherited method directly — no 'stripe.' prefix needed
}
@Override
public boolean refund(String transactionId, double amount) {
return issueRefund(transactionId, Math.round(amount * 100));
}
@Override
public String getStatus(String transactionId) {
StripeChargeResult r = retrieveCharge(transactionId);
return r.success ? (r.status != null ? r.status.toUpperCase() : "UNKNOWN") : "FAILED";
}
}

View File

@@ -0,0 +1,21 @@
package bridge;
/**
* Refined Abstraction — extends RemoteControl with additional capabilities.
*
* The device hierarchy doesn't change at all. TV and Radio
* automatically support mute() and jumpToChannel() because they
* implement Device. Zero new code on the implementation side.
*/
public class AdvancedRemote extends RemoteControl {
public AdvancedRemote(Device device) {
super(device);
}
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);
}
}

View File

@@ -0,0 +1,16 @@
package bridge;
/**
* Implementor — the "implementation" side of the bridge.
* All devices (TV, Radio, SmartSpeaker, etc.) implement this.
* The remote controls only know about this interface, never about specific devices.
*/
public interface Device {
boolean isEnabled();
void enable();
void disable();
int getVolume();
void setVolume(int percent);
int getChannel();
void setChannel(int channel);
String getName();
}

View File

@@ -0,0 +1,29 @@
package bridge;
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.");
}
}

View File

@@ -0,0 +1,32 @@
# 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 -d out/bridge
java -cp out/bridge bridge.Main
```
Requires Java 25.
## Post Section ↔ File Mapping
| Post Section | File(s) |
|---|---|
| Step 1 — The Implementor Interface | `Device.java` |
| Step 2 — Concrete Implementors (TV and Radio) | `TV.java`, `Radio.java` |
| Step 3 — The Abstraction (RemoteControl) | `RemoteControl.java` |
| Step 4 — Refined Abstraction (AdvancedRemote) | `AdvancedRemote.java` |
| Wiring It Together: Main Demo | `Main.java` |
Note: the "Class Explosion Problem — Visualised" pseudocode and the JDBC/SLF4J snippets shown in the article are illustrative only — they are not part of this repository's runnable example.
Article: https://ankurm.com/bridge-design-pattern-java/
All patterns: https://ankurm.com/design-patterns-java/

View File

@@ -0,0 +1,18 @@
package bridge;
// Concrete Implementor #2: Radio
public class Radio implements Device {
private boolean on = false;
private int volume = 20;
private int channel = 1;
@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"; }
}

View File

@@ -0,0 +1,27 @@
package bridge;
/**
* Abstraction — the remote control.
*
* Key point: RemoteControl holds a Device (the bridge) and delegates
* all actual work to it. It adds higher-level semantics on top:
* "togglePower" instead of separate enable()/disable() calls.
*/
public class RemoteControl {
protected Device device; // THE BRIDGE
public RemoteControl(Device device) {
this.device = device;
System.out.println("Remote paired with: " + device.getName());
}
// User action → device operation translation
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); }
}

View File

@@ -0,0 +1,18 @@
package bridge;
// Concrete Implementor #1: Television
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"; }
}

View File

@@ -0,0 +1,39 @@
package composite;
import java.util.ArrayList;
import java.util.List;
/**
* Composite — a directory that holds both Files (leaves) and other Directories.
*
* getSize() is recursive: asks each child for its size and sums them.
* print() recurses with deeper indentation.
*
* The caller doesn't care whether a child is a File or Directory —
* both answer getSize() and print() the same way.
*/
public class Directory implements FileSystemItem {
private final String name;
private final List<FileSystemItem> children = new ArrayList<>();
public Directory(String name) { this.name = name; }
// Fluent add() — allows chaining: dir.add(file1).add(file2).add(subDir)
public Directory add(FileSystemItem item) {
children.add(item);
return this;
}
public void remove(FileSystemItem item) { children.remove(item); }
@Override public String getName() { return name; }
@Override
public long getSize() {
// Each child knows its own size: files return bytes, directories recurse.
// This is the Composite's core: uniform delegation down the tree.
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 + " "); // each level indents 4 more spaces
}
}
}

View File

@@ -0,0 +1,20 @@
package composite;
/**
* Leaf — a single file with no children.
* getSize() returns its own bytes. print() outputs a single line.
* No knowledge of directories or nesting exists in this class.
*/
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);
}
}

View File

@@ -0,0 +1,10 @@
package composite;
/**
* Component — the common interface for both files (leaves) and directories (composites).
* Clients work through this interface and never need to know which type 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 at the given indentation level
}

View File

@@ -0,0 +1,36 @@
package composite;
public class Main {
public static void main(String[] args) {
System.out.println("=== Composite Design Pattern Demo ===\n");
// Build the tree — mix files and directories freely
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 the entire tree — recursion is automatic
System.out.println("File system tree:");
root.print("");
System.out.printf("%nTotal project size: %,d bytes%n", root.getSize());
// The key demonstration: File and Directory through the same interface
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 ===");
}
}

View File

@@ -0,0 +1,30 @@
# Composite Design Pattern — Java Example
**Pattern:** Structural → Composite
**Article:** https://ankurm.com/composite-design-pattern-java/
## What this example shows
Builds a file system tree where files (leaves) and directories (composites) share the same `FileSystemItem` interface. Callers compute size or print the tree without ever checking whether a node is a file or a directory.
## How to run
```bash
javac composite/*.java -d out/composite
java -cp out/composite composite.Main
```
Requires Java 25.
## Post Section ↔ File Mapping
| Post Section | File(s) |
|---|---|
| The Problem: Treating Leaves and Containers Uniformly | illustrative only — not part of this repository's runnable example |
| Step 1 — The Component Interface | `FileSystemItem.java` |
| Step 2 — The Leaf (File) | `File.java` |
| Step 3 — The Composite (Directory) | `Directory.java` |
| Building and Using the Tree | `Main.java` |
Article: https://ankurm.com/composite-design-pattern-java/
All patterns: https://ankurm.com/design-patterns-java/

View File

@@ -0,0 +1,42 @@
package decorator;
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 + "\"\n");
// Stack 1: just trim
TextProcessor trimOnly = new TrimDecorator(new PlainTextProcessor());
System.out.println("Trim only: \"" + trimOnly.process(input) + "\"");
// Stack 2: trim first (innermost), then uppercase (outermost)
// Execution order: PlainText → Trim → UpperCase
TextProcessor trimThenUpper =
new UpperCaseDecorator(
new TrimDecorator(
new PlainTextProcessor()));
System.out.println("Trim + UpperCase: \"" + trimThenUpper.process(input) + "\"");
// Stack 3: trim, filter profanity, then uppercase
TextProcessor full =
new UpperCaseDecorator(
new ProfanityFilterDecorator(
new TrimDecorator(
new PlainTextProcessor())));
System.out.println("Trim + Filter + Upper: \"" + full.process(input) + "\"");
// Stack 4: filter THEN trim — different result because order changed
TextProcessor filterFirst =
new TrimDecorator(
new ProfanityFilterDecorator(
new PlainTextProcessor()));
System.out.println("Filter + Trim: \"" + filterFirst.process(input) + "\"");
System.out.println("\n--- JDK equivalent ---");
System.out.println("new BufferedReader(new InputStreamReader(socket.getInputStream()))");
System.out.println("Same pattern: each wrapper adds one behaviour, order matters.");
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,12 @@
package decorator;
/**
* Concrete Component — the starting point for decoration.
* Returns text unchanged. Decorators wrap around this
* and transform the result one layer at a time.
*/
public class PlainTextProcessor implements TextProcessor {
@Override
public String process(String text) {
return text; // no transformation — base case
}
}

View File

@@ -0,0 +1,14 @@
package decorator;
/** Concrete Decorator: replaces profane 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;
}
}

View File

@@ -0,0 +1,32 @@
# Decorator Design Pattern — Java Example
**Pattern:** Structural → Decorator
**Article:** https://ankurm.com/decorator-design-pattern-java/
## What this example shows
Builds a text-processing pipeline where behaviours (trim, uppercase, profanity filter) are stacked as wrapper objects around a `PlainTextProcessor`, all sharing the `TextProcessor` interface — the same idea behind `new BufferedReader(new InputStreamReader(...))` in the JDK.
## How to run
```bash
javac decorator/*.java -d out/decorator
java -cp out/decorator decorator.Main
```
Requires Java 25.
## Post Section ↔ File Mapping
| Post Section | File(s) |
|---|---|
| Step 1 — Component Interface | `TextProcessor.java` |
| Step 2 — Concrete Component | `PlainTextProcessor.java` |
| Step 3 — Base Decorator | `TextDecorator.java` |
| Step 4 — Concrete Decorators | `UpperCaseDecorator.java`, `TrimDecorator.java`, `ProfanityFilterDecorator.java` |
| Stacking the Decorators — Order Matters | `Main.java` |
Note: the JDK `InputStream`/`BufferedInputStream`/`GZIPInputStream` snippet under "Decorator in the JDK: I/O Streams" is illustrative only — it is not part of this repository's runnable example.
Article: https://ankurm.com/decorator-design-pattern-java/
All patterns: https://ankurm.com/design-patterns-java/

View File

@@ -0,0 +1,22 @@
package decorator;
/**
* Base Decorator — holds a reference to the wrapped TextProcessor
* and delegates to it. Concrete decorators extend this class.
*
* Why an abstract class rather than another interface implementation?
* To avoid repeating the wrapping boilerplate (the field + constructor +
* delegation call) in every single concrete decorator.
*/
public abstract class TextDecorator implements TextProcessor {
protected final TextProcessor wrapped;
protected TextDecorator(TextProcessor wrapped) {
this.wrapped = wrapped;
}
@Override
public String process(String text) {
// Default: pass through to the wrapped processor.
// Concrete decorators call super.process(text) to invoke this,
// then apply their own transformation to the result.
return wrapped.process(text);
}
}

View File

@@ -0,0 +1,9 @@
package decorator;
/**
* Component — defines what all text processors do.
* Both the base implementation AND every decorator implement this.
* Sharing this type is what makes decorators stackable.
*/
public interface TextProcessor {
String process(String text);
}

View File

@@ -0,0 +1,9 @@
package decorator;
/** Concrete Decorator: trims leading and trailing whitespace. */
public class TrimDecorator extends TextDecorator {
public TrimDecorator(TextProcessor wrapped) { super(wrapped); }
@Override
public String process(String text) {
return super.process(text).trim();
}
}

View File

@@ -0,0 +1,13 @@
package decorator;
/** Concrete Decorator: converts result to upper case. */
public class UpperCaseDecorator extends TextDecorator {
public UpperCaseDecorator(TextProcessor wrapped) {
super(wrapped);
}
@Override
public String process(String text) {
// 1. Get result from everything below in the stack
// 2. Apply OUR transformation: uppercase
return super.process(text).toUpperCase();
}
}

View File

@@ -0,0 +1,9 @@
package facade;
/** AudioMixer — normalises audio tracks after conversion. */
class AudioMixer {
static VideoFile fix(VideoFile result) {
System.out.println(" AudioMixer: fixing audio tracks");
return new VideoFile(result.getFilename());
}
}

View File

@@ -0,0 +1,15 @@
package facade;
/** BitrateReader — reads the video buffer and converts it between codecs. */
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());
}
}

View File

@@ -0,0 +1,6 @@
package facade;
/** Codec — common interface implemented by each concrete compression codec. */
interface Codec {
String getName();
}

View File

@@ -0,0 +1,10 @@
package facade;
/** CodecFactory — inspects a VideoFile and returns the matching Codec. */
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();
}
}

View File

@@ -0,0 +1,6 @@
package facade;
/** Concrete Codec: MPEG-4 compression. */
class MPEG4CompressionCodec implements Codec {
@Override public String getName() { return "mpeg4"; }
}

View File

@@ -0,0 +1,30 @@
package facade;
/**
* Facade Design Pattern — Runnable Demo
*
* Demonstrates reducing a complex video conversion subsystem
* to a single method call via a Facade.
*
* Run: javac facade/*.java && java facade.Main
* Article: https://ankurm.com/facade-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Facade Design Pattern Demo ===\n");
VideoConversionFacade converter = new VideoConversionFacade();
System.out.println("-- Client: just one method call --");
String result1 = converter.convertVideo("holiday.ogg", "mp4");
System.out.println("Output: " + result1);
System.out.println();
String result2 = converter.convertVideo("presentation.mp4", "ogg");
System.out.println("Output: " + result2);
System.out.println("\nClient code: 1 line. Subsystem: 6 classes. Facade hides the complexity.");
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,6 @@
package facade;
/** Concrete Codec: Ogg compression. */
class OggCompressionCodec implements Codec {
@Override public String getName() { return "ogg"; }
}

View File

@@ -0,0 +1,35 @@
# Facade Design Pattern — Java Example
**Pattern:** Structural → Facade
**Article:** https://ankurm.com/facade-design-pattern-java/
## What this example shows
A video-conversion subsystem (`VideoFile`, `Codec`, `MPEG4CompressionCodec`, `OggCompressionCodec`, `CodecFactory`, `BitrateReader`, `AudioMixer`) is wrapped behind one class, `VideoConversionFacade`, that exposes a single `convertVideo()` method. Callers never touch the six subsystem classes directly, but they remain public and usable on their own for advanced use cases.
## How to run
```bash
javac facade/*.java -d out/facade
java -cp out/facade facade.Main
```
Requires Java 25.
## Post Section ↔ File Mapping
| Post Section | File(s) |
|---|---|
| The Subsystem (Complex, But Unchanged) — VideoFile | `VideoFile.java` |
| The Subsystem (Complex, But Unchanged) — Codec interface | `Codec.java` |
| The Subsystem (Complex, But Unchanged) — Concrete Codecs | `MPEG4CompressionCodec.java`, `OggCompressionCodec.java` |
| The Subsystem (Complex, But Unchanged) — CodecFactory | `CodecFactory.java` |
| The Subsystem (Complex, But Unchanged) — BitrateReader | `BitrateReader.java` |
| The Subsystem (Complex, But Unchanged) — AudioMixer | `AudioMixer.java` |
| The Facade | `VideoConversionFacade.java` |
| Client Code: One Line | `Main.java` |
Note: the SLF4J `LoggerFactory`, JDBC `DriverManager`, and Spring `JdbcTemplate` snippets under "Facade in the JDK and Real Frameworks" are illustrative only — they are not part of this repository's runnable example.
Article: https://ankurm.com/facade-design-pattern-java/
All patterns: https://ankurm.com/design-patterns-java/

View File

@@ -0,0 +1,39 @@
package facade;
/**
* Facade — the single, simple entry point to a complex video subsystem.
*
* Without this class, clients need to know about CodecFactory,
* BitrateReader, AudioMixer, and VideoFile — 4 classes, dozens of methods.
* The facade reduces that to ONE method call.
*
* The subsystem classes still exist and can be used directly
* by advanced users who need fine-grained control.
*/
public class VideoConversionFacade {
public String convertVideo(String inputFile, String targetFormat) {
System.out.println("VideoConversionFacade: starting conversion of " + inputFile);
// Step 1: open the file and detect codec
VideoFile file = new VideoFile(inputFile);
Codec sourceCodec = CodecFactory.extract(file);
// Step 2: prepare destination codec
Codec destCodec;
if ("mp4".equals(targetFormat)) {
destCodec = new MPEG4CompressionCodec();
} else {
destCodec = new OggCompressionCodec();
}
// Step 3: read, mix audio, encode
VideoFile buffer = BitrateReader.read(file, sourceCodec);
VideoFile intermediateResult = BitrateReader.convert(buffer, destCodec);
VideoFile result = AudioMixer.fix(intermediateResult);
String outputFilename = inputFile.replaceAll("\\.[^.]+$", "." + targetFormat);
System.out.println("VideoConversionFacade: conversion complete -> " + outputFilename);
return outputFilename;
}
}

View File

@@ -0,0 +1,23 @@
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; }
}

View File

@@ -0,0 +1,50 @@
package flyweight;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Flyweight Design Pattern — Runnable Demo
*
* Creates 1000 trees of only 3 species. Without Flyweight: 1000 TreeType
* objects. With Flyweight: 3 TreeType objects (one per species), shared.
*
* Run: javac flyweight/*.java && java flyweight.Main
* Article: https://ankurm.com/flyweight-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Flyweight Design Pattern Demo ===\n");
List<Tree> forest = new ArrayList<>();
Random rnd = new Random(42);
String[][] treeSpecs = {
{"Oak", "dark-green", "rough-bark"},
{"Pine", "blue-green", "needle-texture"},
{"Birch","light-green","smooth-white-bark"}
};
System.out.println("Creating 1,000 trees (only 3 TreeType objects should be created):");
for (int i = 0; i < 1000; i++) {
String[] spec = treeSpecs[i % 3];
TreeType type = TreeFactory.getTreeType(spec[0], spec[1], spec[2]);
forest.add(new Tree(rnd.nextInt(800), rnd.nextInt(600), type));
}
System.out.println("\nForest created. Drawing first 5 trees:");
for (int i = 0; i < 5; i++) {
forest.get(i).draw();
}
System.out.println("\n--- Memory summary ---");
System.out.println("Trees in forest : " + forest.size());
System.out.println("Unique TreeType objects in pool: " + TreeFactory.getPoolSize());
System.out.println("Without Flyweight : 1,000 TreeType objects");
System.out.println("With Flyweight : " + TreeFactory.getPoolSize() + " TreeType objects shared");
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,31 @@
# Flyweight Design Pattern — Java Example
**Pattern:** Structural → Flyweight
**Article:** https://ankurm.com/flyweight-design-pattern-java/
## What this example shows
A forest of 1,000 trees shares just 3 `TreeType` flyweight objects (one per species) instead of allocating a new heavy object per tree. `TreeType` holds the intrinsic (shared) state — name, color, texture. `Tree` holds only the extrinsic (unique) state — x/y position — plus a reference to its shared `TreeType`. `TreeFactory` is the pool manager: `Map.computeIfAbsent()` returns an existing flyweight or creates and caches one.
## How to run
```bash
javac flyweight/*.java -d out/flyweight
java -cp out/flyweight flyweight.Main
```
Requires Java 25.
## Post Section ↔ File Mapping
| Post Section | File(s) |
|---|---|
| Step 1 — The Flyweight (TreeType) | `TreeType.java` |
| Step 2 — The Factory (TreeFactory) | `TreeFactory.java` |
| Step 3 — The Context (Tree) | `Tree.java` |
| Putting the Forest Together | `Main.java` |
Note: the "Flyweight in the JDK: String Pool and Integer Cache" and "Measuring the Benefit" snippets are illustrative only — they are not part of this repository's runnable example.
Article: https://ankurm.com/flyweight-design-pattern-java/
All patterns: https://ankurm.com/design-patterns-java/

View File

@@ -0,0 +1,31 @@
package flyweight;
/**
* Context — stores the UNIQUE (extrinsic) state for each tree instance.
* This is NOT the flyweight itself; it's the lightweight object that
* holds position data and delegates rendering to a shared TreeType.
*
* 10,000 Tree objects × (x:4 bytes + y:4 bytes + reference:8 bytes) = ~160 KB
* vs.
* 10,000 Tree objects × (name + color + texture + x + y) = potentially MBs
*/
public class Tree {
// Extrinsic (unique) state — different per tree
private final int x;
private final int y;
// Reference to the SHARED flyweight
private final TreeType type;
public Tree(int x, int y, TreeType type) {
this.x = x;
this.y = y;
this.type = type;
}
public void draw() {
// Passes extrinsic state (position) into the shared flyweight
type.draw(x, y);
}
}

View File

@@ -0,0 +1,29 @@
package flyweight;
import java.util.HashMap;
import java.util.Map;
/**
* Flyweight Factory — the cache that ensures each unique TreeType
* is only created once, no matter how many trees use it.
*
* This is the piece that makes Flyweight work:
* it intercepts creation requests and returns an existing
* shared instance if one already exists.
*/
public class TreeFactory {
// The pool of shared flyweights
private static final Map<String, TreeType> treeTypes = new HashMap<>();
public static TreeType getTreeType(String name, String color, String texture) {
String key = name + "_" + color + "_" + texture;
// Only create a new TreeType if we haven't seen this combination before
return treeTypes.computeIfAbsent(key, k -> new TreeType(name, color, texture));
}
public static int getPoolSize() {
return treeTypes.size();
}
}

View File

@@ -0,0 +1,29 @@
package flyweight;
/**
* Flyweight — holds the SHARED (intrinsic) state.
* TreeType is shared between all trees of the same species.
*
* If you have 10,000 oak trees, there's ONE OakType object in memory.
* Each individual tree only stores its unique position (extrinsic state).
*/
public class TreeType {
// Intrinsic (shared) state — same for all trees of this species
private final String name;
private final String color;
private final String texture; // imagine a large texture bitmap here
public TreeType(String name, String color, String texture) {
this.name = name;
this.color = color;
this.texture = texture;
System.out.println(" [TreeType created: " + name + "]"); // see how few are created
}
// Extrinsic state (x, y) is passed IN at render time — NOT stored here
public void draw(int x, int y) {
System.out.printf(" Drawing %s tree [%s/%s] at (%d, %d)%n",
name, color, texture, x, y);
}
}

View File

@@ -0,0 +1,11 @@
package proxy;
/**
* Subject interface — defines what both the real object and proxy expose.
* Clients depend on this, not on the concrete class.
*/
public interface DatabaseConnection {
void connect();
String executeQuery(String sql);
void disconnect();
}

View File

@@ -0,0 +1,52 @@
package proxy;
/**
* Virtual Proxy — delays creating the RealDatabaseConnection until
* the first actual query is made. If no query is ever made (e.g.,
* the service is initialized but never used in this request),
* the expensive connection is never opened.
*
* This is exactly how Hibernate proxies work: entities are not
* loaded from the database until you access a field on them.
*/
public class LazyConnectionProxy implements DatabaseConnection {
private final String url;
private RealDatabaseConnection real; // null until first use
public LazyConnectionProxy(String url) {
this.url = url;
System.out.println("[Proxy] Created for " + url + " (real connection NOT opened yet)");
}
// Lazy initialization — create and connect only on first real need
private void initIfNeeded() {
if (real == null) {
System.out.println("[Proxy] First access — initializing real connection...");
real = new RealDatabaseConnection(url);
real.connect();
}
}
@Override
public void connect() {
// Proxy absorbs the connect() call — real connection opened lazily
System.out.println("[Proxy] connect() called — deferring to first query");
}
@Override
public String executeQuery(String sql) {
initIfNeeded(); // NOW we actually need the connection
return real.executeQuery(sql);
}
@Override
public void disconnect() {
if (real != null) {
real.disconnect();
real = null;
} else {
System.out.println("[Proxy] disconnect() called but connection was never opened");
}
}
}

View File

@@ -0,0 +1,41 @@
package proxy;
import java.time.Instant;
/**
* Logging Proxy — adds timing and audit logging around every query
* without touching RealDatabaseConnection or any of its callers.
*
* This is the "cross-cutting concern" use case of Proxy,
* the same mechanism behind Spring AOP's @Around advice.
*/
public class LoggingProxy implements DatabaseConnection {
private final DatabaseConnection target;
public LoggingProxy(DatabaseConnection target) {
this.target = target;
}
@Override
public void connect() {
System.out.println("[Log] connect() at " + Instant.now());
target.connect();
}
@Override
public String executeQuery(String sql) {
long start = System.currentTimeMillis();
System.out.println("[Log] QUERY START: " + sql);
String result = target.executeQuery(sql);
long elapsed = System.currentTimeMillis() - start;
System.out.println("[Log] QUERY END: " + elapsed + "ms | result: " + result);
return result;
}
@Override
public void disconnect() {
System.out.println("[Log] disconnect() at " + Instant.now());
target.disconnect();
}
}

View File

@@ -0,0 +1,51 @@
package proxy;
/**
* Proxy Design Pattern — Runnable Demo
*
* Shows two proxy types:
* 1. Virtual Proxy (lazy connection)
* 2. Logging Proxy (cross-cutting concern)
* 3. Proxy chaining (both together)
*
* Run: javac proxy/*.java && java proxy.Main
* Article: https://ankurm.com/proxy-design-pattern-java/
*/
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Proxy Design Pattern Demo ===\n");
// --- Virtual Proxy: lazy connection ---
System.out.println("-- Virtual Proxy (lazy loading) --");
DatabaseConnection lazy = new LazyConnectionProxy("jdbc:postgresql://localhost/mydb");
lazy.connect(); // absorbed by proxy, no real connection yet
System.out.println("(no real connection yet — saved startup time)");
System.out.println("Result: " + lazy.executeQuery("SELECT * FROM users WHERE id=1"));
System.out.println("Result: " + lazy.executeQuery("SELECT COUNT(*) FROM orders"));
lazy.disconnect();
System.out.println();
// --- Logging Proxy: wraps the real connection ---
System.out.println("-- Logging Proxy --");
DatabaseConnection real = new RealDatabaseConnection("jdbc:mysql://localhost/shopdb");
real.connect();
DatabaseConnection logged = new LoggingProxy(real);
logged.executeQuery("SELECT * FROM products LIMIT 10");
logged.disconnect();
System.out.println();
// --- Proxy chaining: lazy + logging ---
System.out.println("-- Chained Proxies: Lazy + Logging --");
DatabaseConnection chain =
new LoggingProxy(
new LazyConnectionProxy("jdbc:oracle://localhost/warehouse"));
chain.connect();
chain.executeQuery("SELECT SUM(quantity) FROM inventory");
chain.disconnect();
System.out.println("\n=== Demo complete ===");
}
}

View File

@@ -0,0 +1,32 @@
# Proxy Design Pattern — Java Example
**Pattern:** Structural → Proxy
**Article:** https://ankurm.com/proxy-design-pattern-java/
## What this example shows
A `DatabaseConnection` interface is implemented by a real, expensive connection (`RealDatabaseConnection`) and by two proxies that sit in front of it: `LazyConnectionProxy` defers opening the real connection until the first query actually runs (virtual proxy), and `LoggingProxy` wraps any `DatabaseConnection` — real or proxied — and adds timing/audit logging around every call (cross-cutting concern proxy). Because both proxies implement the same interface as the real subject, they compose: `Main` chains `LoggingProxy` around a `LazyConnectionProxy` to get lazy loading and logging together.
## How to run
```bash
javac proxy/*.java -d out/proxy
java -cp out/proxy proxy.Main
```
Requires Java 25.
## Post Section ↔ File Mapping
| Post Section | File(s) |
|---|---|
| The Subject Interface | `DatabaseConnection.java` |
| The Real Subject | `RealDatabaseConnection.java` |
| Virtual Proxy: Lazy Initialisation | `LazyConnectionProxy.java` |
| Logging Proxy: Cross-Cutting Concerns | `LoggingProxy.java` |
| Wiring It Together: Proxy Chaining | `Main.java` |
Note: the "Dynamic Proxy with java.lang.reflect.Proxy" snippet is illustrative only — it is not part of this repository's runnable example.
Article: https://ankurm.com/proxy-design-pattern-java/
All patterns: https://ankurm.com/design-patterns-java/

View File

@@ -0,0 +1,33 @@
package proxy;
/**
* Real Subject — the actual, expensive database connection.
* Opening it takes time. We want to delay this until truly needed.
*/
public class RealDatabaseConnection implements DatabaseConnection {
private final String url;
public RealDatabaseConnection(String url) {
this.url = url;
}
@Override
public void connect() {
System.out.println("[Real DB] Connecting to " + url + " (expensive operation)...");
// Simulate connection setup time
try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
System.out.println("[Real DB] Connected.");
}
@Override
public String executeQuery(String sql) {
System.out.println("[Real DB] Executing: " + sql);
return "ResultSet{rows=42}"; // simulated result
}
@Override
public void disconnect() {
System.out.println("[Real DB] Disconnecting from " + url);
}
}