Imagine that we're creating a new end-to-end POS system for a grocery store called BD Market. Let's assume for the time being that you know (or at least you think you know) everything you need to about the domain. So, like any eager programmer who doesn't want to waste his client's time and money you start creating classes - and here's what you have so far:
class PerishableItem
{
public string Name { get; set; }
public double Price { get; set; }
public double UnitPrice { get; set; }
public uint SKU { get; set; }
public DateTime SellByDate { get; set; }
public DateTime ExpirationDate { get; set; }
protected delegate void ExpiredItemHandler(object sender,ExpiredItemEventArgs e);
public event ExpiredItemHandler Expired;
protected void OnExpired(object item, ExpiredItemEventArgs eArgs)
{
if (this.Expired != null)
{
Expired(item, eArgs);
}
}
}
class NonPerishableItem
{
string Name { get; set; }
double Price { get; set; }
double UnitPrice { get; set; }
uint SKU { get; set; }
}
class Scanner
{
public void Scan(PerishableItem item)
{
///TODO: Update the inventory database using the product's SKU
///TODO: Add the price of this item to the order subtotal
}
public void Scan(NonPerishableItem item)
{
///TODO: Update the inventory database using the product's SKU
///TODO: Add the price of this item to the order subtotal
}
public void CancelScan(PerishableItem item)
{
///TODO: Update the inventory database using the product's SKU
///TODO: Subtract the price of this item from the order subtotal
}
public void CancelScan(NonPerishableItem item)
{
///TODO: Update the inventory database using the product's SKU
///TODO: Subtract the price of this item from the order subtotal
}
}
Notice any problems? If not, then it's a good thing you're reading this. Take another look at the code above and note the following:
- Both PerishableItem and NonPerishableItem have the data members Name, SKU, RetailPrice, and UnitPrice in common.
- Each implementation of the Scanner class's Scan and CancelScan methods does the exact same thing.
So, what can we do to avoid this mess now, and make it such that adding additional item types in the future won't break our Scanner class? We're going to create an interface called IProduct, that's what:
interface IProduct
{
string Name { get; set; }
double Price { get; set; }
double UnitPrice { get; set; }
uint SKU { get; set; }
}
The IProduct interface defines signatures for the public properties that we want to expose in the classes that implement this interface. We can then refactor our Scanner class so that only one Scan() and one CancelScan() implementation is needed. See the updated code below:
class PerishableItem : IProduct
{
#region IProduct Interface Data Members
public string Name { get; set; }
public double Price { get; set; }
public double UnitPrice { get; set; }
public uint SKU { get; set; }
#endregion
//Omitted Code... it's the same as above
}
class NonPerishableItem : IProduct
{
#region IProduct Interface Data Members
string Name { get; set; }
double Price { get; set; }
double UnitPrice { get; set; }
uint SKU { get; set; }
#endregion
}
class Scanner
{
public void Scan(IProduct item)
{
///TODO: Update the inventory database
///TODO: Add the price of this item to the order subtotal
}
public void CancelScan(IProduct item)
{
///TODO: Update the inventory database
///TODO: Subtract the price of this item from the order subtotal
}
}
The biggest (and most helpful) difference you can see is that now we only need one implementation of the Scan() and CancelScan() methods in our Scanner class. This has a future benefit as well, because as long as item types we create in the future implement the IProduct interface we do not have to concern ourselves with updating the Scanner class.
I hope that the brief example above has shown you how using interfaces can help you to write more robust code in your object-oriented projects. Please feel free to send any questions my way at brian (at) brian-driscoll (dot) com, or leave a comment below.
0 comments:
Post a Comment