The Decorator Pattern: It’s the Cherry on Top

Let’s say you’re getting froyo at your favorite local frozen yogurt shop. It starts simple: a dollop of vanilla, a dollop of chocolate. But wait—what if you want sprinkles? Or whipped cream? (Yes, you know it costs extra, and you don’t care.) Now maybe some cookie dough bites, a little caramel sauce, and why not— a few gummy bears. What started as a humble bundle of carbs has been gloriously transformed into something uniquely yours.

Congratulations! You’ve just applied the Decorator pattern, and you didn’t even need a computer science degree to do it.

So… What the Heck Is the Decorator Pattern?

At its core, the Decorator design pattern lets you add new behavior or responsibilities to an object dynamically without messing with the code of the original object. It’s like putting a fancy hat on your dog. The dog’s still a dog, but now it’s got flair. And maybe a monocle.

In more formal terms (just to keep the computer science purists happy), the Decorator pattern is a structural design pattern that wraps an object to provide additional functionality while keeping the same interface. It is a flexible alternative to subclassing…instead of creating multiple subclasses for every possible behavior combination, you can compose behaviors at runtime. it follows the Open/Closed Principle and allows you to extend an object’s behavior without modifying its source code.

Below is the PlantUML class diagram of the decorator pattern. You can also open it up in PlantText by clicking here.

@startuml

!theme toy

title Decorator Pattern - Froyo Example

interface IFroyo {
    +string GetDescription()
    +double Cost()
}

class BasicFroyo {
    +string GetDescription()
    +double Cost()
}

abstract class FroyoDecorator {
    -IFroyo _froyo
    +string GetDescription()
    +double Cost()
}

class SprinklesDecorator {
    +string GetDescription()
    +double Cost()
}

class CherryDecorator {
    +string GetDescription()
    +double Cost()
}

IFroyo <|.. BasicFroyo
IFroyo <|... FroyoDecorator
FroyoDecorator <|-- SprinklesDecorator
FroyoDecorator <|-- CherryDecorator
FroyoDecorator ---> IFroyo : wraps

@enduml
decorator pattern

The Problem It Solves

Imagine you have a class called Froyo. But now you want to add sprinkles and maybe a cherry on top, and so on. You could make subclasses for each combination, sure—but in that way lies madness. You’ll be drowning in a sea of classes with crazy names: FroyoWithSprinkles, FroyoWithCherry, FroyoWithSprinklesAndCherryAndCaramelSauceAndGummyBears.

The Decorator pattern says, “Whoa there, partner. Let’s keep Froyo simple and decorate it with the extras as needed without modifying the original class.”

Real-Life Analogy: Gift Wrapping

Think of it like wrapping a present. The gift itself stays the same, but you can wrap it in glittery unicorn paper, add a giant bow, throw on some curly ribbon, maybe even add a tiny card that sings when opened. Each layer adds something special, but you never touch the gift inside. You’re just jazzing it up on the outside.

A Little Code, Because We’re Not Animals

Let’s break it down with a bit of C# using our Froyo example:

public interface IFroyo
{
    string GetDescription();
    double Cost();
}

public class BasicFroyo : IFroyo
{
    public string GetDescription() => "Basic Froyo";
    public double Cost() => 10.0;
}

public abstract class FroyoDecorator : IFroyo
{
    protected IFroyo _froyo;
    protected FroyoDecorator(IFroyo froyo) { _froyo = froyo; }
    public virtual string GetDescription() => _froyo.GetDescription();
    public virtual double Cost() => _froyo.Cost();
}

public class SprinklesDecorator : FroyoDecorator
{
    public SprinklesDecorator(IFroyo froyo) : base(froyo) { }
    public override string GetDescription() => _froyo.GetDescription() + " + Sprinkles!";
    public override double Cost() => _froyo.Cost() + 1.25;
}

public class CherryDecorator : FroyoDecorator
{
    public CherryDecorator(IFroyo froyo) : base(froyo) { }
    public override string GetDescription() => _froyo.GetDescription() + " + Cherry on top!";
    public override double Cost() => _froyo.Cost() + 0.50;
}

And so now how do you order your bougie custom Froyo?

//Main Program
Console.WriteLine("Let's make some Froyo...");
IFroyo froyo = new BasicFroyo();
Console.WriteLine(froyo.GetDescription() + $", Cost: ${froyo.Cost():0.00}");
Console.WriteLine("");
Console.WriteLine("Add sprinkles...");
froyo = new SprinklesDecorator(froyo);
froyo = new SprinklesDecorator(froyo); // Double spinkles because YOLO
Console.WriteLine(froyo.GetDescription() + $", Cost: ${froyo.Cost():0.00}");
Console.WriteLine("");
Console.WriteLine("Add a cherry...");
froyo = new CherryDecorator(froyo);
Console.WriteLine(froyo.GetDescription() + $", Cost: ${froyo.Cost():0.00}");

Output:

//Output:
Add sprinkles...
Basic Froyo + Sprinkles! + Sprinkles!, Cost: $12.50

Add a cherry...
Basic Froyo + Sprinkles! + Sprinkles! + Cherry on top!, Cost: $13.00

Boom. Froyo shop empire, here you come.

Pros of the Decorator Pattern

  • Flexible: Add or remove behavior at runtime, no need to recompile.
  • Single Responsibility: Each class has its job. No class is overloaded.
  • No Class Explosion: Avoids the combinatorial nightmare of dozens of subclasses.

Cons of the Decorator Pattern

  • Can Get Verbose: Lots of small classes, which some people hate more than pineapple on pizza.
  • Debugging Layers: Wrapping things inside wrappers inside wrappers… you may feel like you’re unpacking Russian nesting dolls during debugging.
  • Order Matters: Putting sprinkles before frosting and you may be doing life wrong. Same with decorators.

Decorator vs Inheritance: The Cage Match

Inheritance is the old-school way of giving a class more features. Inheritance is rigid. Decorators are flexible and cool, like jazz musicians who also code…what! 🙂 No seriously, the decorator is more modular and reusable, easier to test, and decorators also work very well with DI containers, where you can:

  • Register decorators around services
  • Configure behavior composition declaratively (e.g., via attributes or config)

Where You’ve Probably Seen It

You’ve likely encountered the Decorator pattern in the wild:

  • I/O: BufferedReader wraps a FileReader, adding functionality.
  • Web frameworks: Middleware that wraps HTTP requests/responses.
  • UI toolkits: Add borders, scrollbars, shadows to components.

Even that smug reusable component in your favorite JS library might be a decorator in disguise.

Final Thoughts: Layer It On

The Decorator pattern is your secret sauce when you want to add features to an object without blowing up your class hierarchy or rewriting everything from scratch. It’s tasty, flexible, and works great when you need combinations of behavior that change at runtime.

So the next time you’re tempted to subclass your way into oblivion, stop. Breathe. Ask yourself: “Can I just decorate this instead?”

And then, go build your cup of froyo—with extra sprinkles and a cherry on top.

Want more design patterns? Check out our series on the topic.

Scroll to Top