Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

Create a Shopping Cart – Inspired by ASP.NET Daily Articles

A while ago I noticed that one of the ASP.NET Daily Articles was an article on how to create a shopping cart.  Being as I love example code, I went over to the author’s website to take a peek at the sample.  It wasn’t quite what I had hoped for as the shopping cart was just an ArrayList, called _items, filled with objects of type Item that consisted of ProductID, ProductName, Quantity, and UnitPrice.  The example ignored many of the rules of object-oriented development like encapsulation, etc.


I thought I would create a better, yet admittedly imperfect example that may reinforce some of the techniques I have presented in previous posts.  To start, let’s begin with the bare bones Item class that was presented in the ASP.NET daily article example:


 


Item Class (begin)
public class Item
{
private int _productID;
private string _productName;
private int _quantity;
private decimal _unitPrice;

public int ProductID
{
get { return _productID; }
set { _productID = value; }
}

public string ProductName
{
get { return _productName; }
set { _productName = value; }
}

public int Quantity
{
get { return _quantity; }
set { _quantity = value; }
}

public decimal UnitPrice
{
get { return _unitPrice; }
set { _unitPrice = value; }
}
}


 


Let’s pump up this class a bit by overriding Equals and ToString as well as implementing IFormattable that we will use to help display our shopping cart to the console.  Let’s also have the class implement its ItemTotal (_quanity * _price) to help the shopping cart calculate SubTotal.  I consider two Item objects equal if their ProductID’s are equal.  I will use this criteria to determine if I should update the quantity on an existing item vs adding a new item to the shopping cart.


 


Item Class
public class Item : IFormattable
{
#region Private Members

private int _productID;
private string _productName;
private int _quantity;
private decimal _unitPrice;

#endregion

#region Properties

public int ProductID
{
get { return _productID; }
set { _productID = value; }
}

public string ProductName
{
get { return _productName; }
set { _productName = value; }
}

public int Quantity
{
get { return _quantity; }
set { _quantity = value; }
}

public decimal UnitPrice
{
get { return _unitPrice; }
set { _unitPrice = value; }
}

public decimal ItemTotal
{
get { return _quantity * _unitPrice; }
}

#endregion

internal Item() : this(0, string.Empty, 0, 0m) {}

internal Item(int productID, string productName,
int quantity, decimal unitPrice)
{
_productID
= productID;
_productName
= productName;
_quantity
= quantity;
_unitPrice
= unitPrice;
}

#region IFormattable Members

string System.IFormattable.ToString(string format, IFormatProvider formatProvider)
{
if (format == null) format = G;

if (formatProvider != null)
{
ICustomFormatter formatter
= formatProvider.GetFormat(
this.GetType())
as ICustomFormatter;

if (formatter != null)
return formatter.Format(format, this, formatProvider);
}

switch(format)
{
case f : return String.Format({0}{1,10}{2,10}{3,20},
_productName,_quantity.ToString(),_unitPrice.ToString(
c),
ItemTotal.ToString(
c));
case G :
default : return string.Format({0}, _productID.ToString());
}

}

#endregion

#region System.Object Overrides

public override string ToString()
{
return string.Format({0}, _productID.ToString());
}

public override bool Equals(object obj)
{
if (obj == null) return false;
if (Object.ReferenceEquals(this,obj)) return true;
if (this.GetType() != obj.GetType()) return false;

Item objItem = (Item)obj;
if (_productID == objItem._productID) return true;

return false;
}

public override int GetHashCode()
{
return _productID.GetHashCode();
}

#endregion
}


 


Now I need a shopping cart to hold these items.  Let’s create an abstract class, called ShoppingCart, that will define how we want to work with these items in the shopping cart.  As you can see by the class, we are not exposing any implementation details to the developer as to how we are storing and manipulating items.


If later, we think we may need an indexer, we might have to add it.  Right now, the only access to the items in our shopping cart is IEnumerable, which is read-only access to the data that we can bind to a DataGrid, etc. as well as access via foreach.


Notice I also have a CreateItem() method as well.  Ideally I would like to put all these classes in their own assembly.  Using this method coupled with the fact that the Item class constructors are marked internal, we can control the construction of new items to be placed in the shopping cart.  This is from the Creator GRASP Pattern that suggests a good creator of a class (Item) is the class that contains it (ShoppingCart).


 


ShoppingCart Class
public abstract class ShoppingCart : IEnumerable
{
public abstract decimal SubTotal { get; }
public abstract Item CreateItem();
public abstract void AddItem(Item item);
public abstract void DeleteItem(Item item);
public abstract void UpdateItem(Item item);
public abstract IEnumerator GetEnumerator();
}

 


We now need a concrete class that derives from ShoppingCart.  Shown below is my ArrayListCart class that essentially holds item in an ArrayList.  The class uses extensive use of the ArrayList.IndexOf method to decide whether or not an item currently exists in the _items arraylist.  IndexOf uses the Equals method we overrode in the Item class to determine equality.  Also, to keep things easy, I pass through the IEnumerator of _items to be used as the IEnumerator for ArrayListCart.  This is the same technique I discussed in my IEnumerable post.


Notice how I also create a new Item to be placed into _items as opposed to the one passed as an argument.  We don’t want the developer to have a reference to any of the items in the shopping cart.  In this case it is easy to create a new item, but ideally we might want to clone or create a copy constructor for Item.


 


ArrayListCart Class
public class ArrayListCart : ShoppingCart
{
private ArrayList _items;

public override decimal SubTotal
{
get { return GetSubTotal(); }
}

public ArrayListCart()
{
_items
= new ArrayList();
}

#region Public Methods

public override Item CreateItem()
{
return new Item();
}

public override void AddItem(Item item)
{
int index = _items.IndexOf(item);

if (index >= 0)
UpdateItem(item);
else
_items.Add(
new Item(item.ProductID, item.ProductName,
item.Quantity, item.UnitPrice));
}

public override void UpdateItem(Item item)
{
int index = _items.IndexOf(item);

if (index >= 0)
{
Item currentItem
= _items[index] as Item;
currentItem.Quantity
= item.Quantity;
currentItem.UnitPrice
= item.UnitPrice;
}
else
throw new ArgumentException(
String.Format(
Item = {0} is not in the shoppingcart., item));
}

public override void DeleteItem(Item item)
{
int index = _items.IndexOf(item);

if (index >= 0)
_items.RemoveAt(index);
else
throw new ArgumentException(
String.Format(
Item = {0} is not in the shoppingcart., item));

}

#endregion

#region IEnumerable Members

public override IEnumerator GetEnumerator()
{
return (_items as IEnumerable).GetEnumerator();
}

#endregion

#region Private Methods

private decimal GetSubTotal()
{
decimal subTotal = 0m;

foreach (Item item in _items)
subTotal
+= item.ItemTotal;

return subTotal;
}

#endregion
}


 


Here is a simple Factory Method technique that we discussed in a post about the Data Access Application Block to future proof the possibility of creating additional shopping carts or creating one on-the-fly:


 


ShoppingCartFactory Class
public sealed class ShoppingCartFactory
{
private ShoppingCartFactory() {}

public static ShoppingCart CreateShoppingCart()
{
return new ArrayListCart();
}
}


 


Now we need a class to test ArrayListCart, which ideally should have been created using TDD, but alas I didn’t do it.  Therefore, the ArrayListCart class could have all sorts of problems and I just don’t know it :)  Here is some code that when run with the above will output a pretty decent shopping cart to the Console.  Don’t use this as a shopping cart in your e-commerce websites :)


 


Test Class
public class MyClass
{
public static void Main()
{
ShoppingCart cart
= ShoppingCartFactory.CreateShoppingCart();

// Add New Item in Cart
Item apples = cart.CreateItem();
apples.ProductID
= 1;
apples.ProductName
= Apple ;
apples.Quantity
= 5;
apples.UnitPrice
= 1m;

cart.AddItem(apples);

MyClass.DisplayCart(cart);

// Update Existing Item in Cart.
Item moreApples = cart.CreateItem();
moreApples.ProductID
= 1;
moreApples.Quantity
= 10;
moreApples.UnitPrice
= 5m;

cart.UpdateItem(moreApples);

MyClass.DisplayCart(cart);

// Add New Item in Cart
// that already exists in cart.
// It should update quantity.
Item newApples = cart.CreateItem();
newApples.ProductID
= 1;
newApples.ProductName
= Apple ;
newApples.Quantity
= 5;
newApples.UnitPrice
= 1m;

cart.AddItem(newApples);

MyClass.DisplayCart(cart);

// Add New Item in Cart
Item bananas = cart.CreateItem();
bananas.ProductID
= 2;
bananas.ProductName
= Banana;
bananas.Quantity
= 3;
bananas.UnitPrice
= 4m;

cart.AddItem(bananas);

MyClass.DisplayCart(cart);

// Delete Item in Cart
Item deleteItem = cart.CreateItem();
deleteItem.ProductID
= 1;
cart.DeleteItem(deleteItem);

MyClass.DisplayCart(cart);

}

public static void DisplayCart(ShoppingCart cart)
{
Console.WriteLine(String.Format(
{0}{1,13}{2,9}{3,20},
Item,Qty,Price,Item Total));
foreach (Item item in cart)
Console.WriteLine(
{0:f}, item);
Console.WriteLine();
Console.WriteLine(String.Format(
{0,46},
cart.SubTotal.ToString(
c)));
Console.ReadLine();
}
}


 


Unlike the example in the ASP.NET Daily Article, you have absolutely no clue as to how items are being stored, etc. as shown in the test class.  This is ideal, because it gives you more freedom to make changes to the shopping cart without breaking your client code.

This entry was posted in C#. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

2 Responses to Create a Shopping Cart – Inspired by ASP.NET Daily Articles

  1. dhayden says:

    Testing to see if these comments works 😉

  2. David Hayden says:

    I already noticed a bug in the code, which would have been caught by a unit test. When I re-add the same item in the shopping cart, it does not increase the quantity but replace it, because it calls UpdateItem.

    I will leave this to the reader to fix :) Now you understand the value of unit tests.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>