Part 13 - Injecting Service Arrays

13_injecting_service_arrays.png

So this time we're going to look at injecting service arrays ... if you haven't read part 12, it might pay to have a look at that first - this part follows on from the examples presented there.



So this should be reasonably quick, we're just going to rework the Part 12 solution so we don't need decorators... why would we do this, well in this case I feel that rewiring decorators (ie. changing orders, or disabling one of the decorators temporarily) isn't completely intuitive, especially when you have a long chain, it's easy to make mistakes.



So what's the plan, well first off let's implement an abstract class for our calculators:


public abstract class AbstractCalculator
{
public abstract decimal Calculate(decimal currentTotal, Order order);
}


Now we can rework the existing decorators and default calculator to be implementations of the abstract caculator, so here goes:



First off the the logic in the DefaultCostCalculator will be moved into a class called TotalCalculator :


public class TotalCalculator : AbstractCalculator
{
private decimal CalculateTotal(Order order)
{
decimal total = 0;

foreach (OrderItem item in order.Items)
{
total += (item.Quantity*item.CostPerItem);
}

return total;
}

public override decimal Calculate(decimal currentTotal, Order order)
{
return currentTotal + CalculateTotal(order);
}
}



And then for the gst calculator, we do much the same thing:


public class GstCalculator : AbstractCalculator
{
private decimal _gstRate = 1.125m;

public decimal GstRate
{
get { return _gstRate; }
set { _gstRate = value; }
}

private bool IsNewZealand(Order order)
{
return (order.CountryCode == "NZ");
}

public override decimal Calculate(decimal currentTotal, Order order)
{
if (IsNewZealand(order))
{
return (currentTotal*_gstRate);
}

return currentTotal;
}
}



And finally the shipping calculator:


public class ShippingCalculator : AbstractCalculator
{
private decimal _shippingCost = 5.0m;
private decimal _fragileShippingPremium = 1.5m;

public decimal ShippingCost
{
get { return _shippingCost; }
set { _shippingCost = value; }
}

public decimal FragileShippingPremium
{
get { return _fragileShippingPremium; }
set { _fragileShippingPremium = value; }
}

private decimal GetShippingTotal(Order order)
{
decimal shippingTotal = 0;

foreach (OrderItem item in order.Items)
{
decimal itemShippingCost = ShippingCost*item.Quantity;
if (item.IsFragile) itemShippingCost *= FragileShippingPremium;
shippingTotal += itemShippingCost;
}

return shippingTotal;
}

public override decimal Calculate(decimal currentTotal, Order order)
{
return currentTotal + GetShippingTotal(order);
}
}



Now, our original DefaultCostCalculator is reworked to use an array of AbstractCalculators to compute the total cost for an order:


public class DefaultCostCalculator : ICostCalculator
{
private AbstractCalculator[] _calculators;

public DefaultCostCalculator(AbstractCalculator[] calculators)
{
_calculators = calculators;
}

public decimal CalculateTotal(Order order)
{
decimal currentTotal = 0;

foreach (AbstractCalculator calculator in _calculators)
{
currentTotal = calculator.Calculate(currentTotal, order);
}

return currentTotal;
}
}



Notice it takes an array of AbstractCalculator's as a constructor dependency... just like we did for configuration parameters in the early parts of this container tutorial series, we can actually pass arrays of dependencies to a component as well, so last of all let's see the container configuration:




type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />



service="IoC.Tutorials.Part13.AbstractCalculator, IoC.Tutorials.Part13"
type="IoC.Tutorials.Part13.GstCalculator, IoC.Tutorials.Part13">

1.20



service="IoC.Tutorials.Part13.AbstractCalculator, IoC.Tutorials.Part13"
type="IoC.Tutorials.Part13.ShippingCalculator, IoC.Tutorials.Part13">

0.0



service="IoC.Tutorials.Part13.AbstractCalculator, IoC.Tutorials.Part13"
type="IoC.Tutorials.Part13.TotalCalculator, IoC.Tutorials.Part13" />


service="IoC.Tutorials.Part13.ICostCalculator, IoC.Tutorials.Part13"
type="IoC.Tutorials.Part13.DefaultCostCalculator, IoC.Tutorials.Part13">



${calc.total}
${calc.shipping}
${calc.gst}







Notice the default caculator now takes our 3 calculators as members of an array, and if we want to change the order in which they are evaluated, it's trivial to just move them around within the node, or to comment certain ones out.



And just to demonstrate that nothing has changed, the code for the program in part 13 is identical to part 12:


private static void Main(string[] args)
{
WindsorContainer container = new WindsorContainer(new XmlInterpreter());

Order order1 = new Order();
order1.CountryCode = "NZ";
order1.Items.Add(new OrderItem("water", 10, 1.0m, false));
order1.Items.Add(new OrderItem("glass", 5, 20.0m, true));

Order order2 = new Order();
order2.CountryCode = "US";
order2.Items.Add(new OrderItem("sand", 50, 0.2m, false));

ICostCalculator costCalculator = container.Resolve();
Console.WriteLine("Cost to deliver Order 1: {0}", costCalculator.CalculateTotal(order1));
Console.WriteLine("Cost to deliver Order 2: {0}", costCalculator.CalculateTotal(order2));

Console.Read();
}



And the results are the same too:



Cost to deliver Order 1: 192.0000

Cost to deliver Order 2: 260.0




Beautiful :)



The next part will have a quick look at the "Startable" facility, enjoy.
Written on April 28, 2007