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.

Read more.

posted on Monday, March 21, 2005 10:15 PM

Main

News

Green Tea

.NET Development

Enterprise Library

Patterns & Practices