The Decorator Pattern is a classic Structural Design Pattern that’s incredibly powerful when you want to add behaviors to individual objects dynamically at runtime — without modifying the original class.
Here’s your complete English blog post for the Decorator Pattern in Dart, with clear explanations and runnable code examples. Ready to publish 🚀
Design Patterns in Dart: Decorator Pattern
What is the Decorator Pattern?
The Decorator Pattern lets you dynamically add new behaviors or responsibilities to objects without modifying their original structure.
In simpler terms:
“Wrap an object in another object to enhance or modify its behavior.”
When to Use the Decorator Pattern?
- When you want to add features to an object without altering its class.
- When extending behavior via inheritance is not flexible enough.
- When you need a pluggable, layered approach to enhancements.
Real-World Analogy
Think of ordering a coffee ☕.
You start with a base drink (e.g., Espresso), and then you decorate it with extra features like milk, sugar, or whipped cream.
Each decorator adds new behavior — but the base object (coffee) stays unchanged.
Dart Example: Coffee Customization
Step 1: Create a Component Interface
abstract class Coffee {
String getDescription();
double cost();
}
Step 2: Create a Concrete Component
class SimpleCoffee implements Coffee {
@override
String getDescription() => "Simple Coffee";
@override
double cost() => 2.0;
}
Step 3: Create the Abstract Decorator
abstract class CoffeeDecorator implements Coffee {
final Coffee coffee;
CoffeeDecorator(this.coffee);
@override
String getDescription() => coffee.getDescription();
@override
double cost() => coffee.cost();
}
Step 4: Create Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
MilkDecorator(Coffee coffee) : super(coffee);
@override
String getDescription() => "${coffee.getDescription()}, Milk";
@override
double cost() => coffee.cost() + 0.5;
}
class SugarDecorator extends CoffeeDecorator {
SugarDecorator(Coffee coffee) : super(coffee);
@override
String getDescription() => "${coffee.getDescription()}, Sugar";
@override
double cost() => coffee.cost() + 0.3;
}
Step 5: Client Code
void main() {
Coffee coffee = SimpleCoffee();
print("${coffee.getDescription()} -> \$${coffee.cost()}");
coffee = MilkDecorator(coffee);
print("${coffee.getDescription()} -> \$${coffee.cost()}");
coffee = SugarDecorator(coffee);
print("${coffee.getDescription()} -> \$${coffee.cost()}");
}
🧪 Output:
Simple Coffee -> $2.0
Simple Coffee, Milk -> $2.5
Simple Coffee, Milk, Sugar -> $2.8
Benefits of the Decorator Pattern
- Adds behavior without modifying existing code.
- Supports open/closed principle (open for extension, closed for modification).
- More flexible than subclassing — decorators can be combined in any order.
- Enables runtime composition of behaviors.
Drawbacks
- Can result in many small classes.
- Order of decorators matters and may affect behavior.
- Can make debugging more complex due to many layers.
Flutter Use Cases
- Flutter widgets are built using decorator pattern under the hood.
Containerwraps other widgets to apply padding, margin, decoration.Padding,Align,Card,Themeare all decorators.
- Adding runtime behaviors to UI components (e.g., logging, styling).
- Extending services or logic without modifying base classes.
Summary Table
| Attribute | Decorator Pattern |
|---|---|
| Purpose | Add features/behavior dynamically |
| Common Use Cases | UI wrappers, feature toggles, enhancements |
| Easy in Dart? | Yes, using abstract classes & wrapping |
| Useful in Flutter? | Built-in (e.g., Widget wrapping) |



