Decorator Pattern
Add behavior through objects that share a contract, and see why wrapping order changes the result.
· Last updated: 2026-10-24 · 5 min read
A report starts as plain text. One screen needs a title, another needs line numbers, and a third needs both. A subclass for every combination would make each new option multiply the report types.
Decorator adds behavior by wrapping an object in another object with the same calling contract. The four authors of Design Patterns, known as the GoF, classify it as a structural pattern.1
Why the same contract matters
IReport promises a Render() method that returns text. The plain report and both wrappers implement it, so the caller can render any composition in exactly the same way. Each wrapper keeps an inner IReport and delegates to it, meaning it asks that inner object to do the underlying work before adding its own result.
The wrapper’s inner object can itself be another wrapper. That is how title and numbering can be combined without introducing a special TitledNumberedReport class.
One concrete composition
This UML object diagram shows the first composition below. Read the calls from left to right and the returned text from right to left. TextReport supplies the body, NumberedReport numbers its lines, and TitleReport adds a title to that numbered result.
A small executable example
using System;
using System.Linq;
IReport plain = new TextReport("Revenue: 100\nCost: 40");
IReport first = new TitleReport(new NumberedReport(plain), "October");
IReport second = new NumberedReport(new TitleReport(plain, "October"));
Console.WriteLine(first.Render());
Console.WriteLine("---");
Console.WriteLine(second.Render());
public interface IReport
{
string Render();
}
public sealed class TextReport : IReport
{
private readonly string text;
public TextReport(string text) => this.text = text;
public string Render() => text;
}
public sealed class TitleReport : IReport
{
private readonly IReport inner;
private readonly string title;
public TitleReport(IReport inner, string title)
{
this.inner = inner;
this.title = title;
}
public string Render() => title + "\n" + inner.Render();
}
public sealed class NumberedReport : IReport
{
private readonly IReport inner;
public NumberedReport(IReport inner) => this.inner = inner;
public string Render() => string.Join("\n", inner.Render().Split('\n')
.Select((line, index) => $"{index + 1}. {line}"));
}October
1. Revenue: 100
2. Cost: 40
---
1. October
2. Revenue: 100
3. Cost: 40The first composition numbers the body and then adds October, so the title has no number. The second adds the title first and numbers the resulting text, so October becomes line 1. The caller still uses Render() in both cases.
Both compositions refer to the same plain report. These wrappers create new strings and leave that object’s text unchanged.
Where the boundary belongs
A decorator needs to preserve the meaning of its interface as well as its method signature. These wrappers both return report text, which fits IReport. A wrapper that silently sends a report to an external service would add an effect the caller cannot see in that contract.
When a function is enough
A single fixed transformation can be a function. Decorator is useful when callers need the same interface while different object chains add different responsibilities. Strategy chooses a replacement rule; a decorator keeps calling the wrapped object and adds to its behavior.
Related reading
The complete example runs as a .NET 10 console program.
Footnotes
- Gamma, Helm, Johnson and Vlissides. Design Patterns, Decorator. Publisher and pattern catalog. ↩