Decorator Pattern

From Logic Wiki
Jump to: navigation, search

Coffee Shop Example

Video

https://www.youtube.com/watch?v=GCraGHx6gso

Definition

The decorator pattern is about adding extra features to an existing object.

DecoratorPattern.png

A sample implementation in C#

Sequence-diagram-for-top-level-decorator.png

Component

public interface ICoffee
{
    string GetDescription();
    double GetCost();
}

Decorator

public abstract class CondimentDecorator : ICoffee
{
    ICoffee _coffee;
 
    protected string _name = "undefined condiment";
    protected double _price = 0.0;
 
    public CondimentDecorator(ICoffee coffee)
    {
        _coffee = coffee;
    }
 
    public string GetDescription()
    {
        return string.Format("{0}, {1}", _coffee.GetDescription(), _name);
    }
 
    public double GetCost()
    {
        return _coffee.GetCost() + _price;
    }
}

Concrete Decorator

public class MilkDecorator : CondimentDecorator
{
    public MilkDecorator(ICoffee coffee)
        :base(coffee)
    {
        _name = "Milk";
        _price = 0.19;
    }
}
 
public class ChocolateDecorator : CondimentDecorator
{
    public ChocolateDecorator(ICoffee coffee)
        :base(coffee)
    {
        _name = "Chocolate";
        _price = 0.29;
    }
}

Concrete Component

public class Espresso : ICoffee
{
    public Espresso()
    {
        _name = "Espresso";
        _price = 1.29;
    }
}

Usage

var product =  new Milk(new Caramel(new Espresso)))

Test

[Test]
public void ShouldSupportCondiments()
{
    var beverages = new List<icoffee>
    {
        new ChocolateDecorator(new Filtered()),
        new ChocolateDecorator(new MilkDecorator(new Espresso()))
    };
 
    var filteredWithChocolate = beverages.First(); 
    Assert.AreEqual("Filtered with care, Chocolate", 
filteredWithChocolate.GetDescription());
    Assert.AreEqual(1.99 + 0.29, filteredWithChocolate.GetCost());
 
    var espressoWithMilkAndChocolate = beverages.Skip(1).First();
    Assert.AreEqual("Espresso made with care, Milk, Chocolate", 
espressoWithMilkAndChocolate.GetDescription());
    Assert.AreEqual(2.99 + 0.19 + 0.29, espressoWithMilkAndChoco-late.GetCost());
}

Pros

With that, our initial problem is solved in an elegant manner. We:

  • have avoided the class explosion issue
  • are observing the Open/Closed principle
  • can stack as many condiments on top of a beverage as we like

double and triple milk has never been easier!

Cons

All the advantages listed above were gained at the price of adding a couple of classes to our design. This extra complexity is, however, minimal and completely worth our time.

The only thing that doesn't look so nice about the resulting code is the constructor chain:

new ChocolateDecorator(new MilkDecorator(new Espresso()))

That's a good illustration of the encapsulation process that takes place, but it feels fragile and isn’t composable - a Builder object could come in handy.