Decorator Pattern with C# - Real World Tax example
The Decorator pattern is a structural design pattern, which allows the attachment of new behaviours to objects. When it comes to real-world scenarios, this article shows a tax example to exemplify the pattern in C#.
Understanding the problem
How is the Income Tax calculated in your country? The New Zealand tax rate is calculated in tiers, by applying a percentage over a value range:
Personal income
- 39% from $180,001
- 33% from $70,001 to $180,000
- 30%: $48,001 to $70,000
- 17.5%: $14,001 to $48,000
- 10.5%: $0 to $14,000
So if you earn $80,000, it means that 4 tiers will be applied to your income: 10.5%, 17.5%, 30% and 33%, as below:
Income tax rate | Income | Tax |
---|---|---|
Income up to $14,000.00, taxed at 10.5% | 14,000.00 | 1,470.00 |
Income over $14,000.00 and up to $48,000.00, taxed at 17.5% | 34,000.00 | 5,950.00 |
Income over $48,000.00 and up to $70,000.00, taxed at 30% | 22,000.00 | 6,600.00 |
Income over $70,000.00 and up to $180,000.00, taxed at 33% | 10,000.00 | 3,300.00 |
Remaining income over $180,000.00, taxed at 39% | 0.00 | 0.00 |
Total | 80,000.00 | 17,320.00 |
And you will pay $17,320.00
Here, we can observe that different calculations are being applied to a particular income, in other words, different behaviours are being applied, and we can basically solve this problem by applying the Decorator pattern to attach these behaviours to Calculate the Income Tax.
Solving using the pattern
What would be the object that would be served as the glue? Because this is basically what we would have in the end, to get the Tax calculated. Let's call it IncomeTax.cs
:
public class IncomeTax
{
public decimal Income { get; set; }
public decimal Tax { get; set; }
}
We need to define an implementation that will apply the Calculations, the decorators. Let's start with an abstract decorator, that will be able to be extended with different behaviours, following the OCP1 (Open-Close Principle):
public interface ITaxDecorator
{
void CalculateTax(IncomeTax incomeTax);
IncomeTax GetIncomeTax();
}
public abstract class TaxDecorator : ITaxDecorator
{
private IncomeTax _incomeTax = new();
public virtual void CalculateTax(IncomeTax incomeTax)
{
_incomeTax = incomeTax;
}
public IncomeTax GetIncomeTax()
{
return _incomeTax;
}
}
The CalculateTax
is virtual, allowing it to be extended by the decorators.
From here we can start creating our decorators, for the different "tax tiers".
FirstTierTaxDecorator.cs
public class FirstTierTaxDecorator : TaxDecorator
{
public override void CalculateTax(IncomeTax incomeTax)
{
if (incomeTax.Income > Constants.Tax.FIRST_TIER_INCOME) // $14,000.00
{
incomeTax.Tax = Constants.Tax.FIRST_TIER_INCOME * Constants.Tax.FIRST_TIER_RATE; // $14,000.00 * 10.5%
}
else
{
incomeTax.Tax = incomeTax.Income * Constants.Tax.FIRST_TIER_RATE; // Income provided * 10.5%
}
base.CalculateTax(incomeTax);
}
}
SecondTierTaxDecorator.cs
public class SecondTierTaxDecorator : TaxDecorator
{
public override void CalculateTax(IncomeTax incomeTax)
{
if (incomeTax.Income > Constants.Tax.SECOND_TIER_INCOME) // $48,000.00
{
incomeTax.Tax += (Constants.Tax.SECOND_TIER_INCOME - Constants.Tax.FIRST_TIER_INCOME) * Constants.Tax.SECOND_TIER_RATE; // ($48,000.00 - $14,000.00) * 17.5%
}
else if (incomeTax.Income is > Constants.Tax.FIRST_TIER_INCOME and <= Constants.Tax.SECOND_TIER_INCOME) // $14,000.00 < x <= $48,000.00
{
incomeTax.Tax += (incomeTax.Income - Constants.Tax.FIRST_TIER_INCOME) * Constants.Tax.SECOND_TIER_RATE; // (Income - $14,000.00) * 17.5%
}
base.CalculateTax(incomeTax);
}
}
ThirdTierTaxDecorator.cs
public class ThirdTierTaxDecorator : TaxDecorator
{
public override void CalculateTax(IncomeTax incomeTax)
{
if (incomeTax.Income > Constants.Tax.THIRD_TIER_INCOME) // $70,000.00
{
incomeTax.Tax += (Constants.Tax.THIRD_TIER_INCOME - Constants.Tax.SECOND_TIER_INCOME) * Constants.Tax.THIRD_TIER_RATE; // ($70,000.00 - $48,000.00) * 30%
}
else if (incomeTax.Income is > Constants.Tax.SECOND_TIER_INCOME and <= Constants.Tax.THIRD_TIER_INCOME) // $48,000.00 < x <= $70,000.00
{
incomeTax.Tax += (incomeTax.Income - Constants.Tax.SECOND_TIER_INCOME) * Constants.Tax.THIRD_TIER_RATE; // (Income - $48,000.00) * 30%
}
base.CalculateTax(incomeTax);
}
}
FourthTierTaxDecorator.cs
public class FourthTierTaxDecorator : TaxDecorator
{
public override void CalculateTax(IncomeTax incomeTax)
{
if (incomeTax.Income > Constants.Tax.FOURTH_TIER_INCOME) // $180,000.00
{
incomeTax.Tax += (Constants.Tax.FOURTH_TIER_INCOME - Constants.Tax.THIRD_TIER_INCOME) * Constants.Tax.FOURTH_TIER_RATE; // ($180,000.00 - $70,000.00) * 33%
}
else if (incomeTax.Income is > Constants.Tax.THIRD_TIER_INCOME and <= Constants.Tax.FOURTH_TIER_INCOME) // $70,000.00 < x <= $180,000.00
{
incomeTax.Tax += (incomeTax.Income - Constants.Tax.THIRD_TIER_INCOME) * Constants.Tax.FOURTH_TIER_RATE; // (Income - $70,000.00) * 33%
}
base.CalculateTax(incomeTax);
}
}
FifthTierTaxDecorator.cs
public class FifthTierTaxDecorator : TaxDecorator
{
public override void CalculateTax(IncomeTax incomeTax)
{
if (incomeTax.Income > Constants.Tax.FOURTH_TIER_INCOME) // $180,000.00
{
incomeTax.Tax += (incomeTax.Income - Constants.Tax.FOURTH_TIER_INCOME) * Constants.Tax.FIFTH_TIER_RATE; // (Income - $180,000.00) * 39%
}
base.CalculateTax(incomeTax);
}
}
So we end up with Five (5) different behaviours, that are calculating the Income Tax.
Using the pattern
Considering that now all the behaviours were defined, they now can be attached to TaxIncome, and then we have our calculation. The decorators are applied on decorator.CalculateTax(incomeTax);
:
var incomeTax = new IncomeTax()
{
Income = 80000m
};
var decorators = new List<TaxDecorator>
{
new FirstTierTaxDecorator(),
new SecondTierTaxDecorator(),
new ThirdTierTaxDecorator(),
new FourthTierTaxDecorator(),
new FifthTierTaxDecorator()
};
foreach (var decorator in decorators)
{
decorator.CalculateTax(incomeTax);
}
Console.WriteLine($"Income Tax: {incomeTax.Tax}");
In this example, you could see a practical (real-world) example of using decorators to solve a real-world problem.