Tuesday, November 17, 2009

Experimentation with Design Pattern

Note: Download the source code from https://github.com/sommukhopadhyay/SalesTax

I am yet to study the book Pattern Hatching. However, i tried to accumulate all my knowledge on Design Pattern and solved a problem of Accountancy. i would like to share it with you. Let me, first of all, state the problem.

The Problem :

Basic sales tax is applicable at a rate of 10% on all goods, except books, food, and medical products that are exempt. Import duty is an additional sales tax applicable on all imported goods at a rate of 5%, with no exemptions. Also the Sales Tax should be rounded off to the nearest 0.05.

This being the problem, we need to come out with a solution for this problem.

Now let me tell you how my thought process went into it to think it at an abstract level.

First of all, i thought to make two top level classes. One that will define the Items having different benchmarks for Sales Tax and Import Duty; i.e.

1. Food, Medical and Book items which are not imported and hence exempted from both sales tax and import duty
2. Non Food Book and Medical items which are not imported and hence will have just the Sales Tax
3. Food Book and Medical items which are imported and hence will have only import duty
4. Non food book and medical items which are imported and hence will have both Sales Tax and Import Duty

And the other for the Tax Calculation algorithm for different items.

Then i thought this is a perfect match for Strategy Pattern that i had studied in the GoF book. I thought about that pattern keeping in mind for the future expansion of the Tax Calculation Algorithm. What i mean is that for the present problem, the calculation is simple. And hence it does not need any other Strategy. However, for the future purpose if the Tax Calculation Algorithm is changed to some complicated one, then we can just Subclass the Tax Calculation class and attach that strategy to the Item Class.

Next i thought, won't it be nice to get a Factory Class through which the client can create different Items on the fly. Hence i have decided to create an ItemCreator class which is nothing but a parameterized factory class for creating different Items on the fly.

And i came out with the following solution.

The Item class Hierarchy:


File: Item.h

#ifndef ITEM_H
#define ITEM_H

class SalesTax;

//This represents the Items which don't have an Import duty or any sales tax
class Item
{
public:

//Constructors
Item();
Item (SalesTax* aSalesTax);

//Interface Functions for Item

//To calculate the price after tax and import duty
virtual void CalculateTotalPrice();

//To calculate the total tax and import duty
virtual void CalculateTotalTax();

//To set the price of the Items
void SetPrice(double aPrice);

//To get the price of the Items before tax
double getPrice();

//To get the price of the items after tax
double getTotalPrice();

//To get the total tax and import duty of the items
double getTax();

//Data
protected:
//Works as the Strategy of the Sales Tax problem.
//If in future the tax calculation becomes more complicated for different Items
//we will just have to change this Strategy. We can also subclass this Strategy class
//for future expansion of the tax calculation strategy
SalesTax* iSalesTax;
//Data
protected:

//These are the basic properties of any Item.
//Hence these are made protected members so that the subclasses of Item can inherit
//these properties
double iPrice;
double iTotalPrice;
double iTotalTax;
};

//This class represents the Items which have only Import Duty
class ImportedItem : virtual public Item
{
public:
//Constructors
ImportedItem();

//This constructor helps to create Items having only Import duty
ImportedItem(SalesTax* aSalesTax, double aImportDuty);

//Override
virtual void CalculateTotalTax();

protected:
double iImportDuty;
};

//This class represents the Items which have only Sales Tax but no Import Duty

class NonFoodBookMedicalItem : virtual public Item
{
public:
//Constructors
NonFoodBookMedicalItem();

//This constructor helps to create Items having only Sales tax
NonFoodBookMedicalItem(SalesTax* aSalesTax, double aRate);
//Override
virtual void CalculateTotalTax();

protected:
double iRate;
};



//This class represents the Items which have got both Import Duty as well as sales Tax
class NormalItem: public ImportedItem, public NonFoodBookMedicalItem
{
public:
NormalItem();
//This constructor helps to create Items having both Sales tax and Import duty
NormalItem(SalesTax* aSalesTax, double aRate, double aImportDuty);

//Override
virtual void CalculateTotalTax();
};

#endif

As you can see the four classes solve the hierarchy for different items having different benchmark for Sales Tax and import Duty.

The Item.cpp file looks like the following:

File: Item.cpp
#include "SalesTax.h"
#include "Item.h"

Item::Item(){}

Item::Item(SalesTax* aSalesTax):iSalesTax(aSalesTax),iPrice(0),iTotalPrice(0),iTotalTax(0)
{
}

void Item::CalculateTotalPrice()
{
iTotalPrice = iPrice + iTotalTax;
}

double Item::getTotalPrice()
{
return iTotalPrice;
}


void Item::CalculateTotalTax()
{
iTotalTax = iSalesTax->ComputeSalesTax(iPrice, 0, 0);
}

void Item::SetPrice(double aPrice)
{
iPrice = aPrice;
}

double Item::getPrice()
{
return iPrice;
}

double Item::getTax()
{
return iTotalTax;
}
ImportedItem::ImportedItem(){}

ImportedItem::ImportedItem(SalesTax* aSalesTax, double aImportDuty):Item(aSalesTax)
{
iImportDuty = aImportDuty;
}
void ImportedItem::CalculateTotalTax()
{
iTotalTax = iSalesTax->ComputeSalesTax(iPrice, 0, iImportDuty);

}
NonFoodBookMedicalItem::NonFoodBookMedicalItem(){}

NonFoodBookMedicalItem::NonFoodBookMedicalItem(SalesTax* aSalesTax, double aRate):Item(aSalesTax)
{
iRate = aRate;
}

void NonFoodBookMedicalItem::CalculateTotalTax()
{
iTotalTax = iSalesTax->ComputeSalesTax(iPrice, iRate, 0);

}
NormalItem::NormalItem() {}

NormalItem::NormalItem(SalesTax* aSalesTax, double aRate, double aImportDuty):Item(aSalesTax)
{
iRate = aRate;
iImportDuty = aImportDuty;
}
void NormalItem::CalculateTotalTax()
{
iTotalTax = iSalesTax->ComputeSalesTax(iPrice, iRate, iImportDuty);
}

Now let us concentrate on the Sales Tax class

file: SalesTax.h

//This class works as the Strategy of the Sales tax problem
class SalesTax
{
public:

//Default constructor
SalesTax();

//This function helps to compute the Sales Tax
virtual double ComputeSalesTax(double aPrice, double aRate, double aImportduty);

private:
//This is an helper function which will round off the sales tax
double RoundOff(double aTax);
};


And the implementation of the Sales tax is as follow:

file: SalesTax.cpp

#include "SalesTax.h"

SalesTax::SalesTax(){}

double SalesTax::ComputeSalesTax(double aPrice, double aRate, double aImportduty)
{
double tx = (aPrice*aRate/(double(100))) + (aPrice*aImportduty/(double(100)));
return RoundOff(tx);
}
//private:
double SalesTax::RoundOff(double aTax)
{
int taxTemp = (int)aTax;

double decimaltaxTemp = (double)(aTax - (int)taxTemp);

int tempy = (int)(1000*decimaltaxTemp)/100;

int tempz = (int)(1000*decimaltaxTemp - tempy*100);

int temp = (int)(tempz/10);

int t = tempz%10;

if (t >= 5)
temp+=1;

return (double)(taxTemp + tempy*(0.1) + temp*(0.01));
}

As you can see the the data for calculation are being passed from the Item class.

Hence we can say this is in abstract form an implementation of the Strategy Pattern where the Item class is working as the Context and the Sales Tax class is acting as the sole Strategy interface.

Now lets concentrate on the creation of the Items.

As i have already mentioned, this is done through a Parameterized factory class called ItemCreator.

This class looks like the following:

File: ItemCreator.h

#include "Item.h"

const int ITEM_WITH_NOSALESTAX_AND_IMPORTDUTY = 1;
const int ITEM_WITH_NOSALESTAX_ONLY_IMPORTDUTY = 2;
const int ITEM_WITH_ONLY_SALESTAX_AND_NOIMPORTDUTY = 3;
const int ITEM_WITH_BOTH_SALESTAX_AND_IMPORTDUTY = 4;

const double SALES_TAX_RATE = 10;
const double IMPORT_DUTY_RATE = 5;

class Not_A_Standard_Item_Type_Exception
{
public:
void printerrormsg();
};
class ItemCreator
{
public:
virtual Item* Create(int aItemId);
};

And the implementation of this ItemCreator is as follow:

file: ItemCreator.cpp


#include "ItemCreator.h"
#include "Item.h"
#include "SalesTax.h"

using namespace std;

void Not_A_Standard_Item_Type_Exception::printerrormsg()
{
cout <<"Not the right Item Type..." <<endl;
}

Item* ItemCreator::Create(int aItemId)
{
SalesTax* st = new SalesTax();

switch(aItemId)
{
case ITEM_WITH_NOSALESTAX_AND_IMPORTDUTY:
return new Item(st);
break;

case ITEM_WITH_NOSALESTAX_ONLY_IMPORTDUTY:
return new ImportedItem(st,IMPORT_DUTY_RATE);
break;

case ITEM_WITH_ONLY_SALESTAX_AND_NOIMPORTDUTY:
return new NonFoodBookMedicalItem(st,SALES_TAX_RATE);
break;

case ITEM_WITH_BOTH_SALESTAX_AND_IMPORTDUTY:
return new NormalItem(st,SALES_TAX_RATE,IMPORT_DUTY_RATE);
break;

default:
throw Not_A_Standard_Item_Type_Exception();
}
}

And the client program will look like the following:



#include "SalesTax.h"
#include "Item.h"
#include "ItemCreator.h"

#include <vector>

using namespace std;

void main()
{
typedef vector&lt;Item*&gt;  listOfItem; 
listOfItem::iterator theIterator;

listOfItem Basket;
char answer = 'n';

double totalprice = 0;
double totaltax = 0;

do
{
int type_of_item;

cout <<"Enter the type of Item...1,2,3,4" <<endl;

cout <<"1 for ITEM_WITH_NOSALESTAX_AND_NOIMPORTDUTY"  <<endl;

cout <<"2 for ITEM_WITH_NOSALESTAX_ONLY_IMPORTDUTY"<<endl;

cout <<"3 for ITEM_WITH_ONLY_SALESTAX_AND_NOIMPORTDUTY"<<endl;

cout<<"4 for ITEM_WITH_BOTH_SALESTAX_AND_IMPORTDUTY" <<endl;

cin>>type_of_item;

ItemCreator* itemCreator = new ItemCreator();

try 
{
Item* item = itemCreator->;Create(type_of_item);

cout <<"Enter the price of the Item" <<endl;

double price;

cin >>price;

item->SetPrice(price);

Basket.push_back(item);
}

catch(Not_A_Standard_Item_Type_Exception&amp; e)
{
e.printerrormsg();
}

cout<<"Do you want to continue... Y/N" <<endl;
cin>>answer;
}
while (answer =='y');

theIterator = Basket.begin();

int pos = 0;
while (theIterator != Basket.end())
{
Basket.at(pos)->CalculateTotalTax();
totaltax+=Basket.at(pos)->getTax();

Basket.at(pos)->CalculateTotalPrice();

double price = Basket.at(pos)->getPrice();

double price_after_tax = Basket.at(pos)->getTotalPrice();

totalprice+=price_after_tax;
cout<<"Item"  <<pos+1 <<" price " <<price  <<endl;
theIterator++;
pos++; 
}
cout<<"------------" <<endl;
cout<<"Toal tax " <<totaltax <<endl;
cout<<"Total price "<<totalprice<<endl;
}



 Fig : The Class Diagram
Thus the problem is solved using two common design pattern concepts - Strategy Pattern and Parameterized Factory Pattern.

This is the way i am trying to move from the problem domain to the solution domain using design pattern concepts.

Hope this helps others who are studying Design Pattern.

1 comment:

Joy Patra said...

Just a thought - have you considered posting the class diagram of your solution?