The decorator pattern is used to extend or alter the
functionality of an object at runtime. IT does this by wrapping the object with
a decorator class, leaving the original object intact without modification. Let’s
look into the code structure given below for a Starbucks coffee menu
implementation for calculating the cost of the beverages including the various
coffee toppings provided. An inheritance based approach will result in an
object explosion like this.
We can try to reduce the complexity of this design by
refactoring this to a decorator implementation. Later I’ll show how we can use
fluent interfaces to create a fluent decorator that is more readable and a
cleaner approach to use. The final
implementation changes our code structure to something like
We’ll start our refactoring task by creating a base class
for our beverage decorator as given below.
public abstract class Beverage
{
public string Name { get; set; }
public abstract string GetDetails();
public abstract int GetCalories();
public abstract decimal GetCost();
public abstract string GetDescription();
}
public abstract class BeverageDecorator
: Beverage
{
protected Beverage
_beverage;
protected BeverageDecorator(Beverage
beverage)
{
_beverage = beverage;
}
public override string GetDetails()
{
var descriptionBuilder = new
StringBuilder();
descriptionBuilder.Append(_beverage.Name);
descriptionBuilder.AppendLine(_beverage.GetDescription());
descriptionBuilder.AppendFormat(" with
: {0}", Name);
descriptionBuilder.AppendLine();
descriptionBuilder.AppendFormat("Costs
: {0:C}", _beverage.GetCost() + GetCost());
descriptionBuilder.AppendLine();
descriptionBuilder.AppendFormat("Total
calories : {0}g", _beverage.GetCalories() + GetCalories());
descriptionBuilder.AppendLine(_beverage.GetDetails());
descriptionBuilder.AppendLine(GetDescription());
return descriptionBuilder.ToString();
}
}
We include a protected Beverage member in our decorator to
access it and apply extensions on the methods.
Next you can start creating the decorators for the coffee
toppings. I’ve added a sample implementation for the whip cream decorator below
public class CreamDecorator : BeverageDecorator
{
public CreamDecorator(Beverage
beverage) : base(beverage)
{
Name
= "Whip cream";
}
public override int GetCalories()
{
return 100 + _beverage.GetCalories();
}
public override decimal GetCost()
{
return _beverage.GetCost() + .50M;
}
public override string GetDescription()
{
return "Sweetened
and flavored with vanilla.";
}
}
You can now use the beverage object with added toppings as
given below
[TestMethod]
public void
DecoratorAddsFunctionalityAtRuntime()
{
Beverage beverage = new
CafeMisto();
beverage
= new CreamDecorator(beverage);
beverage
= new ChocolateFlakesDecorator(beverage);
beverage
= new CinnamonSprinklesDecorator(beverage);
Assert.IsTrue(beverage.GetCost() > 2M);
Assert.IsTrue(beverage.GetCalories() > 110);
}
Next we can use fluent interfaces approach by adding
extension methods to the Beverage class as
public static class BeverageDecoratorExtensions
{
public static Beverage AddCream(this
Beverage beverage)
{
return new CreamDecorator(beverage);
}
public static Beverage AddChocolateFlakes(this Beverage
beverage)
{
return new ChocolateFlakesDecorator(beverage);
}
public static Beverage AddCinnamonSprinkles(this Beverage
beverage)
{
return new CinnamonSprinklesDecorator(beverage);
}
}
After applying fluent interfaces our decorator
implementation can be applied by using the syntax
[TestMethod]
public void
DecoratorAddsFunctionalityAtRuntime()
{
var beverage = new CafeMisto()
.AddCream()
.AddChocolateFlakes()
.AddCinnamonSprinkles();
Assert.IsTrue(beverage.GetDetails().Contains("Whip cream"));
Assert.IsTrue(beverage.GetCost() > 2M);
Assert.IsTrue(beverage.GetCalories() > 110);
}
No comments:
Post a Comment