Design Patterns in Dart: Decorator Pattern

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.
    • Container wraps other widgets to apply padding, margin, decoration.
    • Padding, Align, Card, Theme are all decorators.
  • Adding runtime behaviors to UI components (e.g., logging, styling).
  • Extending services or logic without modifying base classes.

Summary Table

AttributeDecorator Pattern
PurposeAdd features/behavior dynamically
Common Use CasesUI wrappers, feature toggles, enhancements
Easy in Dart?Yes, using abstract classes & wrapping
Useful in Flutter?Built-in (e.g., Widget wrapping)

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Lên đầu trang