The Bitter Coder Tutorials, Binsor Style: Part XII, Decorators

UML Class diagram of the decorator pattern

Image via Wikipedia

Previous posts in the series:

Today’s post focuses on Decorators.  The Decorator Pattern is a well-known design pattern where functionality is added to a class by wrapping it with another class.  Like an onion, sorta.  Anyway, copying Alex, we are going to use this pattern with Windsor to chain implementations together.

Here is our Order and OrderItem classes:

public class Order

{

private readonly List<OrderItem> _items = new List<OrderItem>();

public List<OrderItem> Items

{

get { return _items; }

}

public string CountryCode { get; set; }

}

public class OrderItem

{

public OrderItem(string name, int quantity, decimal costPerItem, bool isFragile)

{

Name = name;

Quantity = quantity;

CostPerItem = costPerItem;

IsFragile = isFragile;

}

public bool IsFragile { get; set; }

public int Quantity { get; set; }

public decimal CostPerItem { get; set; }

public string Name { get; set; }

}

And an interface to calculate the cost of an order:

public interface ICostCalculator

{

decimal CalculateTotal(Order order);

}

Our default cost calculator (Side note: I like LINQ):

public class DefaultCostCalculator:ICostCalculator

{

public decimal CalculateTotal(Order order)

{

return order.Items.Sum(ord => ord.CostPerItem*ord.Quantity);

}

}

Our Binsor for the DefaultCostCalculator:

component "default.calculator", ICostCalculator,DefaultCostCalculator

Now, the program itself:

static void Main(string[] args)

{

var order1 = new Order

{

CountryCode = “NZ”,

Items =

{

new OrderItem(“water”, 10, 1.0m, false),

new OrderItem(“glass”, 5, 20.0m, true)

}

};

var order2 = new Order

{

CountryCode = “US”,

Items =

{

new OrderItem(“sand”, 50, 0.2m, false)

}

};

var _calc = container.Resolve<ICostCalculator>();

Console.WriteLine(“Cost to deliver Order 1: {0}”, _calc.CalculateTotal(order1));

Console.WriteLine(“Cost to deliver Order 2: {0}”, _calc.CalculateTotal(order2));

}

Running the program, gives us:

Cost to deliver Order 1: 110.0
Cost to deliver Order 2: 10.0

Now, let’s go to New Zealand where we have to pay the Goods and Services Tax.  Here is our cost calculator for that:

public class GstCostCalcualtorDecoarator : ICostCalculator

{

private readonly ICostCalculator _innerCalculator;

private decimal _gstRate = 1.125m;

public GstCostCalcualtorDecoarator(ICostCalculator innerCalculator)

{

_innerCalculator = innerCalculator;

}

public decimal GstRate

{

get { return _gstRate; }

set { _gstRate = value; }

}

#region ICostCalculator Members

public decimal CalculateTotal(Order order)

{

decimal innerTotal = _innerCalculator.CalculateTotal(order);

if (IsNewZealand(order))

{

innerTotal = (innerTotal*_gstRate);

}

return innerTotal;

}

#endregion

private bool IsNewZealand(Order order)

{

return (order.CountryCode == “NZ”);

}

}

And here’s the Decorator pattern.  The GstCostCalculatorDecorator class accepts another ICostCalculator, then modifies the result by adding the GST.  The DefaultCostCalculator is clueless to the GST, which is the beauty inherit in the pattern.

Let’s wire up the Binsor:

component "gst.calculator", ICostCalculator, GstCostCalculatorDecorator:
    innerCalculator=@default.calculator

component "default.calculator", ICostCalculator,DefaultCostCalculator

We put the GST calculator first, so it’s the default implementation, and run the program:

Cost to deliver Order 1: 123.7500
Cost to deliver Order 2: 10.0

So, the US one hasn’t changed, as expected.  Finally, let’s add a shipping calculator:

public class ShippingCostCalculatorDecorator : ICostCalculator

{

private readonly ICostCalculator _innerCalculator;

public decimal ShippingCost { get; set; }

public decimal FragileShippingPremium { get; set; }

public ShippingCostCalculatorDecorator(ICostCalculator innerCalculator)

{

_innerCalculator = innerCalculator;

ShippingCost = 5.0m;

FragileShippingPremium = 1.5m;

}

#region ICostCalculator Members

public decimal CalculateTotal(Order order)

{

decimal innerTotal = _innerCalculator.CalculateTotal(order);

return innerTotal + GetShippingTotal(order);

}

#endregion

private decimal GetShippingTotal(Order order)

{

return order.Items.Sum(item =>

{

var itemShippingCost = ShippingCost*item.Quantity;

if (item.IsFragile) itemShippingCost *= FragileShippingPremium;

return itemShippingCost;

});

}

}

And add our Binsor, making sure it’s the first ICostCalculator implementation in the file:

component "shipping.calculator", ICostCalculator, ShippingCostCalculatorDecorator:
    innerCalculator=@gst.calculator

Run it.

Cost to deliver Order 1: 211.2500
Cost to deliver Order 2: 260.0

As Alex points out, we are calculating our GST before shipping, which is kinda stoopid.  The Decorator Pattern saves our bacon here, as we can just swap things around (oh, and we change some parameters too):

component "gst.calculator", ICostCalculator, GstCostCalculatorDecorator:
    innerCalculator=@gst.calculator
    GstRate=1.20

component "shipping.calculator", ICostCalculator, ShippingCostCalculatorDecorator:
    innerCalculator=@default.calculator
    FragileShippingPremium=0.0

component "default.calculator", ICostCalculator,DefaultCostCalculator

Run it one more time:

Cost to deliver Order 1: 211.2500
Cost to deliver Order 2: 260.0

So, that’s that.  Alex has some great comments in his post about other stuff you can do with the Decorator pattern, so I highly recommend you read his all the way through.

Next time we’ll do this stuff without decorators….

Reblog this post [with Zemanta]
Advertisements

About Ruprict

I am a nerd that is a Nerd Wannabe. I have more kids than should be allowed by law, a lovely wife, and a different sense of humor than most. I work in the field of GIS, where I am still trying to find myself on the map. View all posts by Ruprict

One response to “The Bitter Coder Tutorials, Binsor Style: Part XII, Decorators

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: