Decorator Pattern — OOP and Functional
Implement the decorator pattern both classically and with functional composition for flexible behavior.
import java.util.function.*;
// === Classical OOP approach ===
interface Notifier {
void send(String message);
}
class EmailNotifier implements Notifier {
public void send(String message) {
System.out.println("Email: " + message);
}
}
abstract class NotifierDecorator implements Notifier {
protected final Notifier wrapped;
NotifierDecorator(Notifier wrapped) { this.wrapped = wrapped; }
}
class SlackDecorator extends NotifierDecorator {
SlackDecorator(Notifier wrapped) { super(wrapped); }
public void send(String message) {
wrapped.send(message);
System.out.println("Slack: " + message);
}
}
class SmsDecorator extends NotifierDecorator {
SmsDecorator(Notifier wrapped) { super(wrapped); }
public void send(String message) {
wrapped.send(message);
System.out.println("SMS: " + message);
}
}
// === Functional approach ===
public class DecoratorDemo {
// Function decorators
static <T, R> Function<T, R> withLogging(Function<T, R> fn, String name) {
return input -> {
System.out.printf("[%s] input: %s%n", name, input);
R result = fn.apply(input);
System.out.printf("[%s] output: %s%n", name, result);
return result;
};
}
static <T, R> Function<T, R> withTiming(Function<T, R> fn) {
return input -> {
long start = System.nanoTime();
R result = fn.apply(input);
long ms = (System.nanoTime() - start) / 1_000_000;
System.out.printf("Took %d ms%n", ms);
return result;
};
}
static <T, R> Function<T, R> withRetry(Function<T, R> fn, int maxRetries) {
return input -> {
Exception last = null;
for (int i = 0; i <= maxRetries; i++) {
try { return fn.apply(input); }
catch (Exception e) { last = e; }
}
throw new RuntimeException("Failed after " + maxRetries + " retries", last);
};
}
public static void main(String[] args) {
// OOP: stack decorators
Notifier notifier = new SmsDecorator(
new SlackDecorator(
new EmailNotifier()
)
);
notifier.send("Server is down!");
// Functional: compose decorators
Function<String, String> process = String::toUpperCase;
var decorated = withTiming(withLogging(process, "upper"));
System.out.println(decorated.apply("hello world"));
}
}Use Cases
- Adding behavior to objects without subclassing
- Composable middleware-style processing
- Notification systems with multiple channels
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
Strategy Pattern with Lambdas
Implement the Strategy pattern using interfaces and Java lambdas for flexible algorithm selection.
Best for: Swappable pricing or discount algorithms
Java Streams — Filter, Map, Collect
Process collections with Java Streams: filter, map, flatMap, reduce, and collect to lists or maps.
Best for: Transforming and filtering collections
Builder Pattern — Fluent Object Construction
Implement the Builder pattern for complex objects with validation, immutability, and method chaining.
Best for: Constructing complex objects with many optional parameters
Singleton Pattern — Thread-Safe Approaches
Implement thread-safe singletons in Java: enum, holder class, double-checked locking, and eager init.
Best for: Application-wide configuration managers