In this post, I am going to show you where I have applied the Strategy pattern. I've seen many example on the net, mostly academic examples, but in this post, my example will be based on a real world example. I am using Borland Developer Studio 2006 and Windows Vista.
If you not are familiar with the Strategy pattern, there are a lot of good information out on the net. I will not do any in depth explanation of the pattern here. However, if you are familiar with it, but just don't remember it at the moment, here is the intent of the Strategy pattern according to GoF.
"Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it"
Well, let's start looking into the example I want to show.
Let's say we are working with a program, which is managing items in a stock for a store. The program must handle the items article number, name, weight, price etc.
Now let's say we have some legacy code of a class describing an item.
#ifndef itemH #define itemH #include <System.hpp> class Item { public: Item(const String &name, const String &artNo, float costPriceEuro, float weightKg) : name_(name), artNo_(artNo), costPriceEuro_(costPriceEuro), weightKg_(weightKg) {} float GetCostPrice() const { return costPriceEuro_; } private: String name_; String artNo_; float costPriceEuro_; float weightKg_; }; #endif
Let's say we have a client, which is using the Cost prices. The Cost price can easily be retrieved by calling the items GetCostPrice() method.
Item item("Jeans", "J100-A12", 20.00, 0.450); float costPrice = item.GetCostPrice();
Now, the new requirements tells us that we must be able to handle other kind of prices. More exactly, it must handle the Gross price and the Net price as well.
Before proceeding, just some explanations of the price terms above. I'm not an economist, so don't be too hard on me. Here are some definitions.
The Gross price is the price after the retailer has added the markup margin and the Net price, is the final price you as customer pays.
The formulas for the prices can be written according to;
Gross price = Cost price + Cost price x MarkupPercent/100;
Net price = Gross price - Gross price x DiscountPercent/100;
Now it is time to add the functionality to our Item class. The item must know its markup and discount, so we add those two arguments to the parameter list and also adding methods, so the client can get the prices and percentages.
#ifndef itemH
#define itemH
#include <System.hpp>
class Item
{
public:
Item(const String &name, const String &artNo,
float costPriceEuro, float weightKg,
float markupPercent, float discountPercent)
: name_(name), artNo_(artNo),
costPriceEuro_(costPriceEuro), weightKg_(weightKg),
markupPercent_(markupPercent), discountPercent_(discountPercent)
{}
float GetCostPrice() const { return costPriceEuro_; }
float GetGrossPrice() const;
float GetNetPrice() const;
float GetMarkupPercent() const { return markupPercent_; }
float GetDiscountPercent() const { return discountPercent_; }
private:
String name_;
String artNo_;
float costPriceEuro_;
float weightKg_;
float markupPercent_;
float discountPercent_;
};
#endif
The details for the metods GetGrossPrice and GetNetPrice is given in the implementation file for the item, see below.#include "item.h"
float Item::GetGrossPrice() const
{
return GetCostPrice() + GetCostPrice()*GetMarkupPercent()/100;
}
float Item::GetNetPrice() const
{
return GetGrossPrice() - GetGrossPrice()*GetDiscountPercent()/100;
}
The client then needs to call the methods like below.Item item("Jeans", "J100-A12", 20.00, 0.450, 110, 25); float costPrice = item.GetCostPrice(); float grossPrice = item.GetGrossPrice(); float netPrice = item. GetNetPrice();
We now have a situation, which is clearly applicable for the Strategy pattern.
By using the Strategy pattern, we can encapsulate the price calculation in a class, see the definitions below.
#ifndef strategyH #define strategyH class Item; class Price { public: virtual float Calc(const Item &item) const = 0; }; class CostPrice : public Price { public: virtual float Calc(const Item &item) const; }; class GrossPrice : public Price { public: virtual float Calc(const Item &item) const; }; class NetPrice : public Price { public: virtual float Calc(const Item &item) const; }; #endif
The implementation file for the concrete strategies, looks like below.
#include "strategy.h" #include "item.h" float CostPrice::Calc(const Item &item) const { return item.GetCostPrice(); } float GrossPrice::Calc(const Item &item) const { float costPrice = CostPrice().Calc(item); return costPrice + costPrice*item.GetMarkupPercent()/100; } float NetPrice::Calc(const Item &item) const { float grossPrice = GrossPrice().Calc(item); return grossPrice - grossPrice*item.GetDiscountPercent()/100; }Now we have written our abstract base class and the concrete strategies, so let's use them in the Item implementation. First, let's rewrite the Item class definition.
#ifndef itemH #define itemH #include <System.hpp> class Price; class Item { public: Item(const String &name, const String &artNo, float costPriceEuro, float weightKg, float markupPercent, float discountPercent) : name_(name), artNo_(artNo), costPriceEuro_(costPriceEuro), weightKg_(weightKg), markupPercent_(markupPercent), discountPercent_(discountPercent) {} float GetCostPrice() const { return costPriceEuro_; } float GetPrice(const Price &price) const; float GetMarkupPercent() const { return markupPercent_; } float GetDiscountPercent() const { return discountPercent_; } private: String name_; String artNo_; float costPriceEuro_; float weightKg_; float markupPercent_; float discountPercent_; }; #endif
Above, I've written a general GetPrice function, which takes the strategy as a parameter. The corresponding implementation file now looks like below.
#include "item.h" #include "strategy.h" float Item::GetPrice(const Price &price) const { return price.Calc(*this); }
Finally, the client code can be updated according to below.
Item item("Jeans", "J100-A12", 20.00, 0.450, 110, 25); float costPrice = item.GetPrice(CostPrice()); float grossPrice = item.GetPrice(GrossPrice()); float netPrice = item.GetPrice(NetPrice());
In the future, there may come a new requirement, to implement a new kind of price calculation. In this case, we don't need to modify anything in the Item class, but we need to inherit a new concrete strategy from the abstract base class; Price.
Furthermore, another advantage of using strategies, is showed below. Let's say you need to summarize all GrossPrices and NetPrices of items in a container. Using the first approach with item.GetGrossPrice() and item.GetNetPrice(), may result in two functions, see below.
float GetGrossPriceSum()
{
float sum = 0;
//for each item
sum += item.GetGrossPrice();
//end loop
return sum;
}
float GetNetPriceSum()
{
float sum = 0;
//for each item
sum += item.GetNetPrice();
//end loop
return sum;
}
These functions are doing the exact same things, except for the function name. We can easily simplify this to one function, using the concrete strategy as an input parameter.
float GetPriceSum(const Price &price)
{
float sum = 0;
//for each item
sum += item.GetPrice(price);
//end loop
return sum;
}
I would like to summarize this post with a simple quote from Alan Shalloway/James R. Trott in their book "Design Patterns explained", p. 123.
"Find what varies and encapsulate it"
This is indeed what we are doing in the Strategy pattern. This is something I repeat for myself, when I am designing a new system of my own. Very good to keep in mind.
You are welcome to leave comments, complaints or questions!
No comments:
Post a Comment