Pages

Sunday, May 31, 2020

A simple case of Decorator Pattern

I recently came across a situation where we were calculating the total of a product in one section of our code. The section had multiple ifs and elses to correctly devise the different fees to be add on top of the base price. Debugging that code led to me write this post where we may be able to simplify the code and make it more readable.

In this post, we will take a look at calculating coffee price with different add-ons.

We have coffee which is our base product, then we add different add-ons and each add-on can add a simple price on top of it. And finally leading to tax calculation giving us the total amount.

We can think of this as wrapping the base product (coffee) with each add-on as in following picture:

Coffee with Add-Ons

Thinking in terms of code, each of the layer can be thought of creating a new object and each of those layer takes the object creating before it. So, Creamer takes Coffee object, Sweetner takes Creamer object and so on. In order to achieve this, it makes sense they either derive a common class or implement a common interface. 

Let's call the interface IProduct. Since, we have to calculate the price, let's add GetPrice() as well.

interface IProduct{
double GetPrice();
}

Now, I can create a Coffee class implementing IProduct

class Coffee : IProduct {
public double GetPrice() => 3;
}

What about the add ons? Each add-on should add a price on top of the base product which can be achieved by

class AddOn: IProduct {
private IProduct _addOn;
private double _price;

public AddOn(IProduct addOn, double price){
_addOn = addOn;
_price = price;
}
public double GetPrice() => _price + _addOn.GetPrice();
}

Since AddOn and Coffee both implement IProduct. We can achieve the "wrapping" as follows

var coffee = new Coffee();
var coffeeWithCreamer = new AddOn(coffee, .20);
var coffeeWithCreamerAndSweetener = new AddOn(coffeeWithCreamer, 1);
To get the total price I can simply call
Console.WriteLine(coffeeWithCreamerAndSweetener.GetPrice());
// prints 4.2
    

How about tax? The calculation of tax is not the same as AddOn. In case of tax, we pass percent rather than price. So we create a new class for it also implementing IProduct

class TaxDecorator: IProduct {
private IProduct _addOn;
private double _percent;

public TaxDecorator(IProduct addOn, double percent){
_addOn = addOn;
_percent = percent;
}
public double GetPrice() {
var total = _addOn.GetPrice(); 
return total + ((total * _percent) / 100);
}
}

I specifically use the word "Decorator" in the above class name. This pattern is famously known as the Decorator Pattern. We "decorate" or "wrap" an object with another without changing any behavior of those objects.

So, finally I can add different price on top of our base product, basically decorating it with multiple add-ons, and are able to get the total price at the end. Much more cleaner and readable code than what we have.

var coffee = new Coffee();
var coffeeWithCreamer = new AddOn(coffee, .20);
var coffeeWithCreamerAndSweetener = new AddOn(coffeeWithCreamer, 1);
var totalWithTax = new TaxDecorator(coffeeWithCreamerAndSweetener, 8);
Console.WriteLine(totalWithTax.GetPrice());
// prints 4.536


No comments:

Post a Comment