Dynamic price control with Decorator

This article was originally published in VSJ, which is now part of Developer Fusion.
There’s an interesting consequence of eCommerce on the Web: when companies use localised prices for different regions, it becomes transparently obvious that living in the UK (and the EU in general) is much more expensive than living in the US. So when I was looking at buying some software recently – available via download, involving no retail outlets or shipping – I was somewhat shocked by the price differential that we have to pay on our side of the pond.

Of course, when calculating prices online, retailers often have to allow for many other factors such as local taxation rates, discounts for customers that are upgrading their software, promotional offers and reduced prices for educational customers.

So I began to wonder how I might choose to implement a flexible pricing strategy in, say, an ASP.NET application. This article describes one approach using a standard Gang of Four (GoF) Design Pattern: Decorator.

Getting started

To start with, we need to have some products and prices. These are kept deliberately simple for the purposes of this article, so there are only a few products and their prices (in US$) held inside a simple database table, as shown in Figure 1.

Figure 1
Figure 1: The US$ pricing table

The important thing to note about this table is that the prices are held exclusively in US$. The code that reads the values from this table is not relevant to our discussion (and won’t be examined in this article). Suffice to say that an objective for the model is that we only need to maintain one product price table; not one per country.

We now need some form of “shopping cart” object to hold the customers’ order information. Again, this code is kept deliberately simple as it’s not the focus of the article, so we have two classes, ShoppingCart and LineItem, as shown in Figure 2.

Figure 2
Figure 2: The simplified shopping cart

The following code calculates the current total price of the items in the shopping cart:

public class ShoppingCart
{
	decimal CalculatePrice()
	{
		decimal total = 0;
		foreach( LineItem li in
			lineItems )
		{
			total += (li.Quantity *
				li.Price);
		}
		return total;
	}
// other code elided for clarity
	...
}
As you can see, this code truly is trivial, but it represents a great jumping off point for the discussion that follows.

Supporting localised pricing

Let’s start off by adding support for displaying prices in the user’s local currency. This simply involves converting the decimal value that is returned from the CalculatePrice() method of the ShoppingCart to the appropriate value for the user’s locale, and then displaying it with the correct currency symbol. One approach that we could take would be to create pricing tables for each currency that we want to support, but that would be tedious to maintain and is contrary to the objective that was specified above. What we might therefore prefer to do is to dynamically convert the calculated total, perhaps by calling a Web Service that performs currency conversions. There are a few approaches that spring to mind to implement this functionality, namely:
  1. Add functionality to the ShoppingCart class every time a rule changes, or
  2. Derive new types from ShoppingCart and override the CalculatePrice() method as needed, or
  3. Write a converter type that would convert the value returned from CalculatePrice(). Compose (in some fashion) the converter with the original object.
Option 1 is clearly a laughably poor choice. The impact of changes over time could impose a massive maintenance and testing burden on the code. Similarly, as the calculation rules become more complex our confidence in the code would disappear. Remember a golden OO maxim is to try to make types closed for modification and open for extension. Thus we won’t be choosing option 1.

So which of the other two options should we go for?

If we look at some of the other potential requirements (calculating discounts, applying sales taxes, etc.) then we can see that extending the ShoppingCart might become incredibly difficult. You might, for example, require a type that performed currency conversion and which didn’t calculate sales tax; and another which added sales tax but which disallowed discounting. Simply put, the class hierarchy would rapidly become unmanageable.

So, we’re looking to design an easily extensible model (we just know that new features are going to be required), and we know that inheritance tends to make things difficult when you need to combine two or three different behaviours. This means that we’re going to favour composition over inheritance in our solution.

Given that we’d also typically like to be able to dynamically alter the functionality of the price calculation for a user (maybe by introducing a sale in January), we also need to be careful that we write the client in such a way that it is unaffected by any changes that we make.

Fortunately for us, our trusty old friends the Gang of Four (GoF) have provided a Design Pattern, Decorator, which will enable us to create a pricing calculation engine without having to maintain lots of complicated pricing tables and which we can alter without changing any client code.

Bring on the Decorator Pattern

A simplified UML diagram for the Decorator Pattern is shown in Figure 3.

Figure 3
Figure 3: The UML class diagram for the Decorator Pattern

As you can see from the diagram, the idea behind the Decorator Pattern is that we can decorate (hence the name) an object’s operations with additional functionality simply by composing the object with the decorator. Additionally, we can add many decorators to an object simply by composing multiple decorators together.

So what do we need to do to add a currency conversion decorator to our ShoppingCart? The steps are as follows:

  1. Identify the operation(s) that need to be “decorated”
  2. Extract it/them into an abstract base class or (more likely) an interface
  3. Have the ShoppingCart implement the interface
  4. Create the base Decorator class, which is normally an abstract base class
  5. Derive a concrete implementation of the Decorator that performs the currency conversion
  6. Provide a factory that will create the appropriate object composed out of the relevant decorator type and our ShoppingCart type
So let’s quickly work through this recipe to add support for localised currency conversion.

Identifying the operation and creating the interface

This is (unsurprisingly) straightforward. The operation is the CalculatePrice() method, so let’s extract that into its own interface now:
public interface IPriceCalculator
{
	decimal CalculatePrice();
}
Referring back to Figure 2, then IPriceCalculator is the “Component” at the top of the diagram, with CalculatePrice() as the “Operation” member.

Creating the concrete type

The ShoppingCart class is the “ConcreteComponent” type in Figure 3. Note that it must implement the IPriceCalculator interface that we’ve specified which involves making one minor tweak to the class as shown below:
public class ShoppingCart :
	IPriceCalculator
{
	List<LineItem> lineItems =
		new List<LineItem>();

	public void AddLineItem(
		LineItem li )
	{
		lineItems.Add( li );
	}

	public decimal CalculatePrice()
	{
		decimal total = 0;
		foreach( LineItem li
			in lineItems )
		{
			total += (li.Quantity *
				li.Price);
		}
		return total;
	}
}
Note how blissfully unaware the ShoppingCart remains about any price conversions that are about to happen. This will make it easy to add even more functionality to the price calculation engine over time without ever having to revisit our ShoppingCart type: which is wonderful news from a testing perspective.

Creating the base Decorator type

Our base decorator type needs to implement the same interface as the underlying concrete type (ShoppingCart) so that the client code can remain completely unaware that a decorator object even exists. Of course, this assumes that the client code is programmed against the interface, rather than the underlying concrete type. However, after the previous articles in this series I’m assuming that you’re more than happy with coding against interfaces.

I’ve chosen to implement the decorator by having it take a reference to the object that it’s decorating via a parameter in its constructor. You don’t need to do this, and some implementations use a publicly settable property.

However, I tend to find that passing the reference in to the constructor makes the most sense, as the purpose of a decorator is to add value to another object. Clearly, the decorator won’t be in a usable state until it holds that reference, but there might be implementation issues where having a property is the better approach.

Of course, the type of the reference that is passed in will be that of the interface that was defined in step 1 of the recipe, resulting in our base decorator type looking as follows:

public abstract class
	PriceCalculatorDecorator :
		IPriceCalculator
{
	IPriceCalculator priceCalculator;

	protected IPriceCalculator
		PriceCalculator
	{
		get { return priceCalculator; }
	}

	public PriceCalculatorDecorator(
		 IPriceCalculator pc )
	{
		priceCalculator = pc;
	}

	public virtual decimal
		CalculatePrice()
	{
		return
priceCalculator.CalculatePrice();
	}
}

Creating a concrete decorator

We now need to create a concrete decorator type, which in our case will perform currency conversion.

For this implementation I decided to use a Web Service that I found on Xmethods to perform the US$ to other currency conversions, resulting in a CurrencyConversionDecorator class (with all error checking omitted) that looks as follows:

public class
	CurrencyConversionDecorator :
		PriceCalculatorDecorator
{
	string country;

	public CurrencyConversionDecorator(
		string country,
			IPriceCalculator pc )
			: base( pc )
	{
		this.country = country;
	}

	public override decimal
		CalculatePrice()
	{
		decimal price =
			base.CalculatePrice();
		CurrencyExchangePortTypeClient
			client = new
	CurrencyExchangePortTypeClient();
// Use Web Service to get conversion
// rate
		float rate = client.getRate(
			"US", country );
		return price * (decimal) rate;
	}
}
Note that the source currency rate is always US$, because that is the currency that we’re holding in the underlying database table. The implementation of IPriceCalculator.CalculatePrice() simply locates the conversion rate for the target country and returns the total price obtained so far, multiplied by that rate.

Creating the factory

The final step in the recipe is to create a factory that will return the correctly composed object. This factory can be as complex or as simple as you like, but in order to constrain our example to a manageable scope, here is a simple factory implementation that makes use of our decorator to add currency conversion for British, French and German customers. Of course, US customers don’t need to use a decorator, so the ShoppingCart object that is obtained from the user’s Session is returned directly.
// Obtains an appropriately decorated
// ShoppingCart depending on
static IPriceCalculator
	GetPriceCalculator()
{
	IPriceCalculator ret =
		(IPriceCalculator)
		HttpContext.Current.Session[
			"ShoppingCart"];
	switch(
		HttpContext.Current.Request.
		UserLanguages[0].ToLower() )
	{
		case "fr-fr":
		case "de-de":
			ret = new
			CurrencyConversionDecorator(
							 "euro", ret );
			break;
		case "en-gb":
			ret = new
			CurrencyConversionDecorator(
							"UK", ret );
			break;
		case "en-us":
			break;
	}
	return ret;
}
As you can see from the above code, if the user is French or German then we wrap their ShoppingCart object with a decorator that converts the US $ price into Euros; if the user is British, the ShoppingCart is wrapped with a decorator that converts the price into Pounds; and if they’re American then no decorator is used.

As far as the client is concerned, it really doesn’t need to know that the decorators are involved. It simply calls into the GetPriceCalculator() method to obtain an IPriceCalculator reference to an object so that it can determine the price to be displayed, as shown in the following code:

...
IPriceCalculator calc =
	GetPriceCalculator();
string price = String.Format(
	Resources.TotalPriceFormatString,
		calc.CalculatePrice() );
...
As an aside, basing your pricing decisions just on the Accept-Language HTTP header might be considered a little flaky: the user might simply change them to see if they can get a better price.

So where are we?

Figure 4 shows the current state of the important parts of the code in pictorial form:

Figure 4
Figure 4: Implementing the Decorator Pattern

I’ve annotated the Visual Studio 2005 class diagram with the names from the UML diagram in Figure 3 to make it easier to see how the implementation code maps onto the Decorator Pattern.

Right now we seem to have done an awful lot of work to simply change one value into another based on a currency conversion factor. If truth be told, this is a lot of work and highlights a fairly typical consequence of many of the design patterns: a proliferation of fairly small types within the code.

However, the true power of the Decorator Pattern is about to be revealed, and we get to see the real payback for what we’ve achieved so far, as we update the calculation engine so that it adds sales tax for only the UK customers.

Adding support for sales tax

Unfortunately, living within the UK makes me liable to pay Value Added Tax (VAT) on many of the goods that I buy. Similarly, in Singapore and Australia you would have to pay GST, and in California it would be State Sales Tax.

The great news is that adding support for sales tax is a breeze now that we’ve used the Decorator Pattern for our price calculations.

Implementing the SalesTaxDecorator

The next listing shows the implementation code for a simple SalesTaxDecorator type that adjusts the price to reflect any applicable sales tax.
public class SalesTaxDecorator :
		PriceCalculatorDecorator
{
	decimal taxRate;

	public SalesTaxDecorator(
		decimal taxRate,
		IPriceCalculator pc ): base(pc)
	{
		this.taxRate = taxRate;
	}

	public override decimal
		CalculatePrice()
	{
		decimal price =
			base.CalculatePrice();
		return price * taxRate;
	}
}
Understandably, this code is very similar to the currency conversion decorator, but instead of obtaining a conversion factor via a Web Service call it simply multiplies the price by the specified tax rate.

Applying VAT to the UK consumer

So how do we inflict VAT on the UK consumer? Only one line of code needs to be added, and again the client need not be aware of the change because that line of code is within the simple factory method previously shown. The revised implementation now looks like this:
static IPriceCalculator 
	GetPriceCalculator()
{
	IPriceCalculator ret =
		(IPriceCalculator)
			HttpContext.Current.
			Session["ShoppingCart"];
	switch(
		HttpContext.Current.Request.
		UserLanguages[0].ToLower() )
	{
		case "fr-fr":
		case "de-de":
			ret = new
			CurrencyConversionDecorator(
					"euro", ret );
				break;
		case "en-gb":
			ret = new
			CurrencyConversionDecorator(
					"UK", ret );
			ret = new SalesTaxDecorator(
				1.175, ret );
			break;
		case "en-us":
			break;
	}
	return ret;
}
Of course, in the real world we would implement the SalesTaxDecorator in such a way that it read the tax rates from a database table, probably keyed by country and/or state. I certainly wouldn’t advocate that variable data such as tax rates be stored within code, due to the expense involved in re-building and re-testing the code when changes are made: remember the code above is deliberately simplified to emphasise the power of the pattern. It’s also likely that different products will attract different rates of tax, so again I will plead the simplification argument.

The important thing to note from this code is the simple way that the CurrencyConversionDecorator and the SalesTaxDecorator are connected together to add the functionality of them both to the CalculatePrice() operation for the user’s ShoppingCart. We can visualise this as shown in Figure 5.

Figure 5
Figure 5: The composed price calculator for the UK customer

As you can see, assuming that the ShoppingCart object contains items with a combined value of $5,000 then the price returned to a UK customer would be £2937.50, assuming that the currency conversion ratio is £1 = $2 and that VAT is applied at 17.5%.

A truly extensible model

The real benefit of the Decorator pattern is demonstrated by how easy it becomes to extend or alter the functionality of an object dynamically. For example, if the Marketing department suddenly decides that they want to offer a 10% discount during January, then instead of having to change all the prices in the underlying tables just for a month, all you need to do is add a new JanuarySalesDecorator type as follows:
public class JanuarySalesDecorator :
		PriceCalculatorDecorator
{
	public JanuarySalesDecorator(
		IPriceCalculator pc)
		: base( pc )
	{}
	public override decimal
		CalculatePrice()
	{
		decimal total =
			base.CalculatePrice();
		if( DateTime.Now.Month == 1 )
		{
			total = total * 0.9m;
		}
		return total;
	}
}
You can then drop this into the following factory method:
static IPriceCalculator
	GetPriceCalculator()
{
	IPriceCalculator ret =
		(IPriceCalculator)
		HttpContext.Current.Session[
		"ShoppingCart"];
	ret = new
		JanuarySalesDecorator( ret );
	switch(
		HttpContext.Current.Request.
			UserLanguages[0].ToLower() )
	{
		case "fr-fr":
		case "de-de":
			ret = new
			CurrencyConversionDecorator(
					 "euro", ret );
			break;
		case "en-gb":
			ret = new
			CurrencyConversionDecorator(
						"UK", ret );
			ret = new SalesTaxDecorator(
				1.175, ret );
			break;
		case "en-us":
			break;
		}
	return ret;
}
The beauty of this approach is that there is absolutely zero impact on any of the other types that are in the code base, reducing the footprint of code that needs to be built and (exhaustively) tested.

This is cool! What must I do to use the Decorator Pattern?

The extensibility that the Decorator Pattern offers doesn’t come without cost. We’ve already mentioned one consequence, namely that you can end up with a fair number of extra types in the code, but there are a couple of other important constraints imposed upon you, as follows:
  1. That you will program against interfaces/abstract types (aka Contracts), rather than against concrete types
  2. That you will use a factory (or occasionally the Builder or Prototype patterns) to make the objects that will be decorated
When you think about how the decorators work, it rapidly becomes obvious that you have to program against the interfaces. This enables the client to be coded without any knowledge of whether it is talking directly to the original object or to a decorator. And if you’re working with interfaces or abstract types in the client then clearly it cannot use new to instantiate objects. You will therefore need to write some form of factory to create the object, and add its decorators, on behalf of the client.

How complex that factory becomes will depend on the nature of what you’re decorating and how truly dynamic you want your code to be. I’ve used a Simple Factory implementation in this article, which happens to contain significant business logic about how the price should be calculated for each locale. However, it’s not beyond the realms of imagination to think that this logic might itself be held in a database or configuration file. Our final listing, for example, uses an XML file to define which decorators should be loaded for the different locales, based on the same logic as we’ve seen previously:

<?xml version="1.0"
	encoding="utf-8" ?>
<decorators>
	<decoratorGroup locale="*">
	<!-- Add to all locales -->
		<decorator type="DMW.Demos.
			JanuarySalesDecorator" />
	</decoratorGroup>
	<decoratorGroup locale="en-uk">
		<decorator type="DMW.Demos.
		CurrencyConversionDecorator" >
			<constructorParameter
			name="country" value="UK" />
		</decorator>
		<decorator type="DMW.Demos.
				SalesTaxDecorator">
			<constructorParameter
		name="taxRate" value="1.175" />
		</decorator>
	</decoratorGroup>
	<decoratorGroup
		locale="fr-fr, de-de">
		<decorator type="DMW.Demos.
		CurrencyConversionDecorator" >
			<constructorParameter
		name="country" value="euro" />
		</decorator>
	</decoratorGroup>
</decorators>
With a bit of imagination, you can see how we might use the Reflection APIs in .NET to create the decorators on the fly inside the factory, resulting in zero code changes when adding new decorators.

Conclusion

In this article we looked at how we might add functionality to how the total price of a ShoppingCart is calculated based on the user’s locale: for us that involved supporting currency conversion, adding sales tax and applying a seasonal discount. The approach that we took was to use the Decorator Pattern, as it allows us to add new functionality as needed without modifying existing code.

Specifically, neither the ShoppingCart nor the client code needed to be modified as we plugged in additional pieces of functionality, until we ended up with the capabilities as shown in Figure 6.

Figure 6
Figure 6: Price calculation functionality by country

So how do the GoF summarise the Decorator Pattern in their book, “Elements of Reusable Object-Oriented Software”? The quick definition reads as follows:

Decorator. Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.”
As you’ve seen in the code above, decorators really do provide a flexible and dynamic alternative to subclassing. Which means that if tomorrow we needed to add support for, say, Singapore which requires a conversion to Singaporean Dollars, adding GST but which allows no seasonal discount (it’s never cold in Singapore), then we know that no code changes would be required other than to a factory method or configuration file.

And that’s got to be a good thing.


David Wheeler is a freelance trainer and consultant, specialising in skills transfer to teams working with Microsoft .NET. He helps moderate Microsoft’s ASP.NET forums, is a regular speaker at Bearpark’s annual DevWeek conference, and will also be presenting sessions on how to use Design Patterns in .NET at Software Architect 2007 on 12–14 June. He can be contacted at [email protected].

You might also like...

Comments

About the author

Dave Wheeler United Kingdom

Dave Wheeler is a freelance instructor and consultant who specialises in .NET application development. He’s a moderator on Microsoft’s ASP.NET and Silverlight forums and is a regular speaker at ...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“I invented the term Object-Oriented, and I can tell you I did not have C++ in mind.” - Alan Kay