DumpsterDoggy

Articles

Search
Newest Articles
Recent Tutorials
Related Blogs
Get Microsoft Silverlight
Twitter Updates

Bookmark This

Leveraging Windsor and the Strategy Pattern

Chris Missal, November 13, 2008

I'm still working to learn valuable patterns and practices that are applicable, but I think I've come up with quite a decent "strategy pattern" or "specification pattern" here. I'm not quite sure what this would be considered. To put this example into plain english, here is what is going on:

  1. An IOrder has a shipping fee and this needs to be calculated.
  2. The rules to calculate the fee are different depending on the IOrder's current state.
  3. We need to give the IOrder object several ways to validate and calculate the shipping fee.
  4. Upon construction of an IOrder object, we need to ensure it has all possible IShippingCalcaulators.
  5. All IShippingCalcaulators should follow the same contract (IShippingCalculator).
  6. We can inject a list of IShippingCalculators to the IOrder's constructor upon instantiation using an IoC container.
  7. Our Windsor configuration file will ensure all of our IShippingCalcaulators are passed into the IOrder.
  8. Now the IOrder has all the possible calculators; when the Shipping Fee is requested, it can determine the fee based only on valid IShippingCalculators.

First we'll need to regeister our IShippingCalculator components:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="MadeUpStatsWindsorSchema">

  <components>
    <component id="OvernightShippingCalculator"
       service="DumpsterDoggy.Domain.Shipping.Calculators.IShippingCalculator, 
            DumpsterDoggy.Domain"
       type="DumpsterDoggy.Domain.Shipping.Calculators.OvernightShippingCalculator, 
            DumpsterDoggy.Domain" />

    <component id="SemiTruckShippingCalculator"
       service="DumpsterDoggy.Domain.Shipping.Calculators.IShippingCalculator, 
            DumpsterDoggy.Domain"
       type="DumpsterDoggy.Domain.Shipping.Calculators.SemiTruckShippingCalculator, 
            DumpsterDoggy.Domain" />

    <component id="StandardFreightShippingCalculator"
       service="DumpsterDoggy.Domain.Shipping.Calculators.IShippingCalculator, 
            DumpsterDoggy.Domain"
       type="DumpsterDoggy.Domain.Shipping.Calculators.StandardFreightShippingCalculator, 
            DumpsterDoggy.Domain" />

    <component id="IOrder"
       service="DumpsterDoggy.Domain.Orders.IOrder, DumpsterDoggy.Domain"
       type="DumpsterDoggy.Domain.Orders.Order, DumpsterDoggy.Domain"
       lifestyle="transient">
      <parameters>
        <shippingCalculators>
          <list>
            <item>${OvernightShippingCalculator}</item>
            <item>${SemiTruckShippingCalculator}</item>
            <item>${StandardFreightShippingCalculator}</item>
          </list>
        </shippingCalculators>
      </parameters>
    </component>
  </components>
</configuration>

Nothing very crazy going on there, just a very simple windsor configuration. If you download the code, you'll see that these classes that implement the IShippingCalculator interface have their own unique rules on what the fee should be. Since these are injected into the constructor of the Order, that Order can get a list of fees by giving each calculator a reference to itself. It can also see if the calculator is even valid to be calculating a fee for that Order. Let's take a look at the IShippingCalculator interface now.

namespace DumpsterDoggy.Domain.Shipping.Calculators
{
    public interface IShippingCalculator
    {
        bool IsValidFor(IShippable shippable);

        decimal CalculateCharge(IShippable shippable);

        string GetName();
    }
}

Alright, so now that we have our interface defined and our container configured, we just have to resolve our Type and all of our configured calculators should be passed into the constructor. Each concrete implementation will calculate the charge for the "shippable" item. Now, onto the fun part, the Shippable object (in this example, is the Order object which Implements IOrder which extends IShippable) has two methods: CalculateLowestShippingFee and CalculateHighestShippingFee. These use Linq to find the values in which to return.

public Order(IList<IShippingCalculator> shippingCalculators)
{
    this.shippingCalculators = shippingCalculators;
}

public decimal CalculateLowestShippingFee()
{
    return shippingCalculators.Where(c => c.IsValidFor(this))
        .Min(c => c.CalculateCharge(this));
}

public decimal CalculateHighestShippingFee()
{
    return shippingCalculators.Where(c => c.IsValidFor(this))
        .Max(c => c.CalculateCharge(this));
}

Once you get something like this running in a project with tests around it... it just works. There's very little you need to worry about and it feels like it just "magically" does the trick. Maybe that's just me. I like it, see if it works for you and I'm happy to answer any questions if you contact me and I'd love to see some comments on this as well.

You can download the source code to this example if you wish.

Filed Under: .Net   C#   IoC   Open Source   Strategy Pattern