Structural Design Patterns
Imagine you're building a notification system. You start with a simple Notifier class that sends messages.
Soon, requirements expand. The business wants to add optional ways to send notifications: via SMS, then Facebook, then maybe encrypting the message, or compressing it first. If you use inheritance to handle these combinations, your class hierarchy explodes.
You would need:
SmsNotifier, FacebookNotifier, EncryptedNotifierSmsAndFacebookNotifier, EncryptedSmsNotifierEncryptedSmsAndFacebookNotifier...This approach is unmanageable. The number of classes grows exponentially with each new feature. The hierarchy is rigid, and you violate the Open/Closed Principle every time you need a new combination. How can you add these responsibilities to objects dynamically without creating a new class for every possible feature set?
The Decorator is a structural design pattern that lets you attach new behaviors to objects by placing them inside special "wrapper" objects that contain the behaviors.
The core idea is to create a set of "decorator" classes that "wrap" the original object. These decorators share the same interface as the object they wrap, so they are transparent to the client. You can "stack" multiple decorators on top of each other, with each one adding its own specific responsibility. It's like putting on clothes: you start with a person (the core object), then you can "decorate" them with a shirt, then a jacket, then a scarf. Each item adds something new without changing the person underneath.
INotifier).EmailNotifier).Component interface. It holds a reference to a wrapped Component object. Its main purpose is to delegate all method calls to the object it wraps, providing a common base for all concrete decorators.Base Decorator and adds its own behavior either before or after delegating the call to the wrapped object. (e.g., SmsDecorator, EncryptionDecorator).Let's build our flexible notification system using the Decorator pattern.
Step 1: The Component Interface
public interface INotifier {
void send(String message);
}
Step 2: The Concrete Component
This is our basic, default notifier.
public class EmailNotifier implements INotifier {
@Override
public void send(String message) {
System.out.println("Sending core notification via Email: " + message);
}
}
Step 3: The Base Decorator
This abstract class will be the superclass for all our specific decorators.
public abstract class NotifierDecorator implements INotifier {
protected final INotifier wrappedNotifier;
public NotifierDecorator(INotifier notifier) {
this.wrappedNotifier = notifier;
}
@Override
public void send(String message) {
// Default behavior is to delegate to the wrapped object
wrappedNotifier.send(message);
}
}
Step 4: Concrete Decorators
Each decorator adds one specific piece of functionality.
public class SmsDecorator extends NotifierDecorator {
public SmsDecorator(INotifier notifier) {
super(notifier);
}
@Override
public void send(String message) {
super.send(message); // First, do the original notification
sendSms(message); // Then, add our new functionality
}
private void sendSms(String message) {
System.out.println("...also sending via SMS: " + message);
}
}
public class EncryptionDecorator extends NotifierDecorator {
public EncryptionDecorator(INotifier notifier) {
super(notifier);
}
@Override
public void send(String message) {
String encryptedMessage = encrypt(message);
// We modify the data BEFORE delegating it to the wrapped object
super.send(encryptedMessage);
}
private String encrypt(String message) {
return "Encrypted(" + message + ")";
}
}
Step 5: Client Code
The client can now compose complex notification behaviors at runtime.
INotifier notifier = new EmailNotifier(); // Start with the core object
notifier = new SmsDecorator(notifier); // Wrap it with the SMS decorator
notifier = new EncryptionDecorator(notifier); // Wrap it again with the encryption decorator
notifier.send("Your order has shipped!");
// Output:
// ...also sending via SMS: Encrypted(Your order has shipped!)
// Sending core notification via Email: Encrypted(Your order has shipped!)
Benefits:
Drawbacks:
The Decorator pattern is a cornerstone of flexible and extensible object-oriented design.
FileInputStream with decorators like BufferedInputStream (to add buffering) and GZIPInputStream (to add decompression), all of which share the same InputStream interface.