- Attach additional responsibilities to an object dynamically.
- Decorators provide a flexible alternative to sub-classing for extending functionality.
- The intent of the Decorator design pattern is to let you extend an object's behavior dynamically.
- This ability to dynamically attach new behavior to objects is done by a Decorator class that "wraps itself" around the original class.
- The Decorator pattern combines polymorphism with delegation.
- It is polymorphic with the original class so that clients can invoke it just like the original class.
- In most cases, method calls are delegated to the original class and then the results are acted upon, or decorated, with additional functionality.
- Decoration is a flexible technique because it takes place at run-time, as opposed to inheritance which take place at compile time.
- Examples of the Decorator in the .NET Framework include a set of classes that are designed around the Stream class.
- The Stream class is an abstract class that reads or writes a sequence of bytes from an IO device (disk, sockets, memory, etc).
- The BufferedStream class is a Decorator that wraps the Stream class and reads and writes large chunks of bytes for better performance.
- Similarly, the CryptoStream class wraps a Stream and encrypts and decrypts a stream of bytes on the fly.
- Both BufferedStream and CryptoStream expose the same interface as Stream with methods such as Read, Write, Seek, Flush and others.
- Clients won't know the difference with the original Stream.
- Decorator classes usually have a constructor with an argument that represents the class they intent to decorate:
new BufferedStream(Stream stream).
- The extension methods are a close cousin to this pattern as they also offer the ability to add functionality to an existing type (even if the type is sealed).
- Similarly, attached properties and attached events which are used in WPF, also allow extending classes dynamically without changing the classes themselves.
Participants
Component
(IPizzaItem)
- Defines the interface for objects that can have responsibilities added to them dynamically.
ConcreteComponent
(RegularPizza, LargePizza)
- Defines an object to which additional responsibilities can be attached.
Decorator
(PizzaItemDecorator)
- Maintains a reference to a
Component
and defines an interface that conforms to Component
's interface.
ConcreteDecorator
(Cheese, Mushroom)
- Adds responsibilities to the
Component
.
var regularPizza = new RegularPizza();
Console.WriteLine(regularPizza.Info());
var regularPizzaAndCheese = new Cheese(regularPizza);
Console.WriteLine(regularPizzaAndCheese.Info());
var largePizza = new LargePizza();
Console.WriteLine(largePizza.Info());
var largePizzaAndCheese = new Cheese(largePizza);
Console.WriteLine(largePizzaAndCheese.Info());
var largePizzaAndMushroomAndCheese = new Mushroom(largePizzaAndCheese);
Console.WriteLine(largePizzaAndMushroomAndCheese.Info());
public interface IPizzaItem
{
int Calorie { get; set; }
string Info();
}
public class RegularPizza : IPizzaItem
{
public int Calorie { get; set; } = 500;
public string Info() => "Regular Pizza";
}
public class LargePizza : IPizzaItem
{
public int Calorie { get; set; } = 800;
public string Info() => "Large Pizza";
}
public abstract class PizzaItemDecorator : IPizzaItem
{
public int Calorie { get; set; }
protected IPizzaItem PizzaItem;
public PizzaItemDecorator(IPizzaItem pizzaItem)
{
PizzaItem = pizzaItem;
}
public virtual string Info() => PizzaItem.Info();
}
public class Cheese : PizzaItemDecorator
{
protected IPizzaItem _pizzaItem;
public Cheese(IPizzaItem pizzaItem) : base(pizzaItem)
{
Calorie = pizzaItem.Calorie + 300;
}
public override string Info() => $"{base.Info()} and extra cheese";
}
public class Mushroom : PizzaItemDecorator
{
protected IPizzaItem _pizzaItem;
public Mushroom(IPizzaItem pizzaItem) : base(pizzaItem)
{
Calorie = pizzaItem.Calorie + 100;
}
public override string Info() => $"{base.Info()} and extra mushroom";
}
Rules of Thumb
- Adapter provides a different interface to its subject.
- Proxy provides the same interface. Decorator provides an enhanced interface.
- Adapter changes an object's interface, Decorator enhances an object's responsibilities.
- Decorator is thus more transparent to the client.
- As a consequence, Decorator supports recursive composition, which isn't possible with pure Adapters.
- Composite and Decorator have similar structure diagrams, reflecting the fact that both rely on recursive composition to organize an open-ended number of objects.
- A Decorator can be viewed as a degenerate Composite with only one component.
- However, a Decorator adds additional responsibilities - it isn't intended for object aggregation.
- Decorator is designed to let you add responsibilities to objects without subclassing.
- Composite's focus is not on embellishment but on representation.
- These intents are distinct but complementary.
- Consequently, Composite and Decorator are often used in concert.
- Composite could use Chain of Responsibility to let components access global properties through their parent.
- It could also use Decorator to override these properties on parts of the composition.
- Decorator and Proxy have different purposes but similar structures.
- Both describe how to provide a level of indirection to another object, and the implementations keep a reference to the object to which they forward requests.
- Decorator lets you change the skin of an object.
- Strategy lets you change the guts.