Decorator Pattern
The Decorator Pattern is a structural design pattern that allows you to dynamically attach additional responsibilities and behaviors to an object at runtime. This is achieved by wrapping the object in a series of decorator objects, each adding its own behavior before or after delegating the task to the original object. This pattern provides a flexible alternative to subclassing for extending functionality.
The General Problem It Solves
The primary problem that the Decorator Pattern solves is the limitation of static inheritance. Inheritance is a powerful mechanism but it has its drawbacks:
Example of the General Problem
Consider a simple text editor application. Initially, the editor can only display plain text. Users request additional features such as the ability to render text in bold, italic, and underline styles. Using inheritance, you might create subclasses like BoldText, ItalicText, and UnderlineText. However, if users want to combine these styles, you'll need subclasses like BoldItalicText, BoldUnderlineText, ItalicUnderlineText, and so on. This quickly leads to a combinatorial explosion of subclasses.
Generalizing the When
You should use the Decorator Pattern when:
The How
Step 1: Component Interface: Define an interface that both concrete components and decorators will implement.
// Component Interface
public interface Text {
String getText();
}
Step 2: Concrete Component: Create a class that implements the component interface and defines the core behavior.
领英推荐
// Concrete Component
public class PlainText implements Text {
private String text;
public PlainText(String text) {
this.text = text;
}
@Override
public String getText() {
return text;
}
}
Step 3: Base Decorator: Implement a base decorator class that also implements the component interface and contains a reference to a wrapped object. It delegates all operations to this wrapped object.
// Base Decorator
public abstract class TextDecorator implements Text {
protected Text decoratedText;
public TextDecorator(Text text) {
this.decoratedText = text;
}
@Override
public String getText() {
return decoratedText.getText();
}
}
Step 4: Concrete Decorators: Extend the base decorator to add additional behaviors.
// Concrete Decorator for Bold
public class BoldText extends TextDecorator {
public BoldText(Text text) {
super(text);
}
@Override
public String getText() {
return "<b>" + decoratedText.getText() + "</b>";
}
}
// Concrete Decorator for Italic
public class ItalicText extends TextDecorator {
public ItalicText(Text text) {
super(text);
}
@Override
public String getText() {
return "<i>" + decoratedText.getText() + "</i>";
}
}
// Concrete Decorator for Underline
public class UnderlineText extends TextDecorator {
public UnderlineText(Text text) {
super(text);
}
@Override
public String getText() {
return "<u>" + decoratedText.getText() + "</u>";
}
}
Step 5: Client Code: Use the decorators to wrap the concrete component dynamically.
public class TextEditor {
public static void main(String[] args) {
Text text = new PlainText("Hello, World!");
text = new BoldText(text);
System.out.println(text.getText());
text = new ItalicText(text);
System.out.println(text.getText());
text = new UnderlineText(text);
System.out.println(text.getText());
}
}
Pros:
Cons:
Conclusion
The Decorator Pattern is a powerful and flexible tool for extending the behavior of objects dynamically. By wrapping objects in a series of decorators, you can build up complex behaviors without the need for a proliferation of subclasses. While the pattern introduces some complexity, its benefits in terms of flexibility, composability, and adherence to the Single Responsibility Principle often outweigh the trade-offs. Using our text editor example, we’ve seen how this pattern can be applied in a practical scenario, demonstrating its effectiveness in real-world applications.
Thank you