A Variant of the Strategy/State Pattern Example
This is one common variant to the strategy/state pattern that I commonly use. It varies in that I use a second object to actually figure out which stategy/state pattern object should be used in a given situation. For instance, suppose we had the following strategy pattern base class.
public abstract class TaxCalculator
{
public abstract decimal CalculateTaxAmount(Product product);
public abstract bool IsCorrectProduct(Product product);
public abstract decimal IsExemptFromTax(Product product);
}
So this is the base class. The key is the IsCorrectState method. This method determines which strategy pattern should be used; normally, in the state/strategy pattern examples, you see the corred State/Strategy object simply being instantiated. My approach works a little different. Before we get to that, let's look at an example:
public class OneHundredPercentJuiceCalculator : TaxCalculator
{
public override decimal CalculateTaxAmount(Product product)
{
return 0;
}
public override bool IsCorrectProduct(Product product)
{
return (product.Sku == 150483 || product.Sku == 434523);
}
public override decimal IsExemptFromTax(Product product)
{
return true;
}
}
Notice the IsCorrectProduct definition; it's the one that identifies which products qualify for evaluation. In this implementation, another object uses the collection of tax calculators to calculate taxes. Check out the TaxManager clas below.
public class TaxManager
{
private List<TaxCalculator> _calculators = new List<TaxCalculator>();
private TaxManager() { }
public static TaxManager GetInstance()
{
TaxManager mgr = new TaxManager();
mgr._calculators.Add(new OneHundredPercentJuiceCalculator());
mgr._calculators.Add(new CandyCalculator());
mgr._calculators.Add(new MilkCalculator());
mgr._calculators.Add(new CerealCalculator());
return mgr;
}
public bool IsExempt(Product product)
{
var calculator = _calculators.First(i => i.IsCorrectProduct(product));
return calculator.IsExemptFromTax(product);
}
public decimal CalculateTaxAmount(Product product)
{
var calculator = _calculators.First(i => i.IsCorrectProduct(product));
return calculator.CalculateTax(product);
}
}
The tax manager instantiates a list of all the TaxCalculator objects. In this example, the list of calculators is sealed, but this example could be expanded to allow other calculators to be registered. Whatever calculator is appropriate for the product is retrieved via the IsCorrectProduct method. This method evaluates every calculator to find the correct calculator for a given product. The calculator is returned, and its other method called; you can see this approach is like the Provider pattern in this example; though the TaxManager class is not static, it could be adapted to be.
In my opinion, this approach makes it easy to spread logic across multiple objects, while minimizing the amount of work that each method does. This could be achieved by adding additional helper methods to the base class, so each derived class has less to do. Additionally, there could be intermediate abstract classes that sit between the TaxCalculator class and the final derived classes that help make working with this pattern easier too. It all depends on the needs of the application.
For instance, suppose that alot of products return a six percent tax value and are not exempt. Another helper class could be defined as:
public abstract class SixPercentTaxCalculator : TaxCalculator
{
public override decimal CalculateTaxAmount(Product product)
{
return 1.06;
}
public override bool IsExemptFromTax(Product product)
{
return false;
}
}
Now, as long as classes inherit from this, those classes only have to inherit the IsCorrectCalculator method to figure out which calculator is correct.