| |
And,
here is another Tax Calculator concrete strategy implementation
for another country (Singapore) : |
| |
|
public class SingaporeTaxCalculationStrategy
: TaxCalculationStrategyBase
{
// Default constructor
public SingaporeTaxCalculationStrategy()
{
}
// Singapore may use
tax calculation
// formula different from Japan
public override void CalculateTax()
{
base.orderInfo.TaxAmount =
base.orderInfo.OrderAmount * 10 / 100;
}
}
|
| |
We
also make use of a factory to give us a specific Tax
Calculator. |
| |
Below
is one version of the factory |
| |
This
class is the point of change when we try to add more
calculators. |
| |
This
version, though, requires code compilation. |
| |
|
public class TaxCalculatorFactory
{
// Context objects uses this static method
// to create a specific tax calculator.
public static TaxCalculationStrategyBase
CreateTaxCalculator()
{
TaxCalculationStrategyBase calculator = null;
// We can retrieve
a tax calculator name by using
// an application configuration file.
string calculatorName =
System.Configuration.ConfigurationSettings.AppSettings
["TaxCalculator"];
// We can add appropriate
case statements
// to new calculators.
switch (calculatorName)
{
case "Japan":
calculator = new JapanTaxCalculationStrategy();
break;
case "Singapore":
calculator =
new SingaporeTaxCalculationStrategy();
break;
// Add more case statements here.
// case "Philippine":
// calculator =
// new PhilippineTaxCalculationStrategy();
// break;
}
return calculator;
}
}
|
| |
Below
is another version of a factory. |
| |
We
can use reflection to instantiate the type of the calculator
based on a configuration file. |
| |
This way, we only change the configuration
file with no code compilation steps needed. The configuration
file entry includes the full name of the class that
will be instantiated. |
| |
|
public class TaxCalculatorFactory
{
public static TaxCalculationStrategyBase
CreateTaxCalculator()
{
// We assume the calculator class is located
in
// the same assembly as the client.
// But we can easily load it from any assembly.
Assembly assembly = Assembly.GetExecutingAssembly();
// App.config: ["TaxCalculatorName"]
values -
// "TaxCalculators.SingaporeTaxCalculatorStrategy"
string className =
System.Configuration.ConfigurationSettings.AppSettings
["TaxCalculatorName"];
// Use reflection
to create an instance.
// Make sure that we cast the result of the
// CreateInstance method.
TaxCalculationStrategyBase calculator;
calculator =
(TaxCalculationStrategyBase)
assembly.CreateInstance(className);
return calculator;
}
} |
| |
Here
is the context class that uses the tax calculators: |
| |
|
public class OrderInfo
{
// Default Constructor
public OrderInfo()
{
}
// Context class contains
or knows only
// the abstract class but has no knowledge
// of any concrete strategy classes.
protected TaxCalculationStrategyBase calculator;
// Provides a method
to set a strategy
public void SetTaxCalculationStrategy
(TaxCalculationStrategyBase calculator)
{
this.calculator = calculator;
}
// Client can call
this method for tax computation.
public void CalculateTax()
{
// Use the factory to create calculator instance.
this.calculator =
TaxCalculatorFactory.CreateTaxCalculator();
// Set the context
of the strategy.
// This type of assignment is often called
// dependency injection.
calculator.SetTaxableInfo(this);
// This effectively
sets the TaxAmount property.
calculator.CalculateTax();
}
// Other properties
of the context class.
private int orderNo;
public int OrderNo
{
get
{
return orderNo;
}
set
{
orderNo = value;
}
}
private double orderAmount;
public double OrderAmount
{
get
{
return orderAmount;
}
set
{
orderAmount = value;
}
}
private double taxAmount;
// This method will be called by the concrete
strategy
// using dependency injection
// or otherwise also termed "inversion
of control"
public double TaxAmount
{
get
{
return taxAmount;
}
set
{
taxAmount = value;
}
}
public double TotalAmount
{
get
{
return orderAmount + taxAmount;
}
}
|
| |
We
can use a client with the following code: |
| |
|
public partial class OrderForm
: Form
{
public OrderForm()
{
InitializeComponent();
}
private void calculateButton_Click
(object sender, EventArgs e)
{
// Instantiate the
context object
OrderInfo orderInfo = new OrderInfo();
// Assign properties
orderInfo.OrderNo = 9999;
orderInfo.OrderAmount =
double.Parse(this.orderAmountTextBox.Text);
// Call tax calculation
orderInfo.CalculateTax();
// Show results.
this.lblOrderAmount.Text =
orderInfo.OrderAmount.ToString();
this.lblTaxAmount.Text =
orderInfo.TaxAmount.ToString();
}
}
|
| |
Several
things to note here : |
 |
The context class (OrdeInfo) does
not have to know which specific tax calculator it
is using. It only knows the base abstract class (TaxCalculationStrategyBase).
The factory takes care of the creation of the specific
calculator based on the configuration file. |
| |
 |
The
client class (OrderForm) does not have to know any
tax calculators at all. The tax calculation procedure
is encapsulated inside the context class (OrderInfo) |
| |
 |
If we want to
use a specific calculator, we simply change the configuration
file |
 |
If
we want to create another tax calculator for another
country, for example, Philippine taxation, we simple
derived from the base abstract class (TaxCalculationStrategyBase)
and code inside the specific tax calculation implementation. |
| |
| |
In conclusion, the strategy design
pattern allows us to encapsulate an algorithm that
varies into its own classes. This way we avoid code
complexity inside the context class. |
| |
|
| |
References: |
| |
* Design Patterns Elements of Reusable Object Oriented
Software by Gamma, Helm, Johnson, Vlissides
|
| |
|
| |
For
more information on this solution please email at contactus@blastasia.com
|
| |
|
|
|
| |
|
|
|
| |
|
|