Tuesday, February 16, 2010

Explaining C# Abstraction, Inheritence and Polymorphism

Far too often, fundamental programming concepts like inheritance and polymorphism are taught using vague examples, like class "AA" and class "BB". Let's get away from that; they're far too abstract to offer a proper explanation. We'll use a workshop as our example instead.

Say you have a program to simulate a workshop, and you want to create a class for each possible tool you can use… hammers, power saws, screwdrivers, etc. Pretty much every tool class you create is going to have common methods, like Use() and PutAway(), regardless of what it actually is. Inheritance allows you to enforce this common ground between tools, and reap some very useful benefits in the process. Take the following "base" class:

public abstract class Tool()
{

private double _price;
public double GetPrice()
{
return _price;
}

public abstract void Use();
public abstract void PutAway();

}

This class doesn't do much of anything on it's own, but it provides a common interface for all of the other tools you create.

Every class that inherits from Tool will automatically have a _price, a method called "GetPrice()", and two other methods called Use() and PutAway(). Because we've already written code for GetPrice(), any other Tool class we write can take advantage of it right away.

Use() and PutAway() are a little different. Because we've declared Use() and PutAway() as abstract methods, any class we write that inherits from Tool [i]must[/i] have its own Use() and PutAway() methods. You might ask, "then why bother putting them in the base Tool class in the first place, if you have to rewrite them in all the inherited classes, anyway"? The reason is because these abstract methods [i]guarantee[/i] to our program that any tool it ever deals with will have these methods, 100% certain. We'll see the benefits of this in a minute, but first let's take a quick look at two classes that inherit from Tool (in place of specific code, I'll just add some comments):

public class ScrewDriver: Tool
{

public override void Use()
{
// Insert into screw head and rotate
}

public override void PutAway()
{
// Find correct drawer in toolbench and place inside
}

}

public class PowerSaw: Tool
{

public override void Use()
{
// Flip "on" switch and apply blade to wood surface
}

public override void PutAway()
{
// Flip "off" switch and place on shelf
}

public void ReplaceBatteries()
{
// Remove old batteries, and insert new ones
}

}

Both ScrewDriver and PowerSaw inherit the Use() and PutAway() methods from Tool, but in addition to this they can implement their own unique methods, as well. For example, PowerSaw has a ReplaceBatteries() method, which would be pointless on a screwdriver, but important on a power tool. And that's a critical point: that's why we didn't put a ReplaceBatteries() method in the tool class… because not [i]every[/i] tool needs a ReplaceBatteries() method. Instead, we placed it in the class that would actually need it. Use() and PutAway(), however, are needed by every tool, and so those methods are in the base class.

Note also that, although you can't see it, both ScrewDriver and PowerSaw also have _price and GetPrice(), because they inherited it from "Tool". For example, the following code is, in theory, perfectly legal:

ScrewDriver myScrewDriver;
myScrewDriver.GetPrice();

What about Use() and PutAway()? Those are actually written in ScrewDriver and PowerSaw, so why do we even care that they're included in Tool? For one thing, it guarantees that you won't forget to add those methods to your inherited classes. For another thing, you can do stuff like this:

Tool[] myArrayOfTools = new Tool[2];
tool[0] = new ScrewDriver();
tool[1] = new PowerSaw();

foreach( Tool myTool in myArrayOfTools )
{
myTool.Use();
myTool.PutAway();
}

And that will be perfectly legal, even though element 0 and element 1 in that array are totally different types! One is a ScrewDriver, and one is a PowerSaw, but because they are both tools, we can call their Use() and PutAway() methods regardless of their specific type.

A few closing points…

1) The "abstract" in "public abstract class Tool()" just means that we can't create instances of our Tool class… which is good, because the actual base Tool class doesn't do anything. We don't want people creating copies of it by accident, they should stick to ScrewDrivers and PowerSaws that can actually do stuff.

2) If we want to write code for Use() in the base class, but still allow it to be overridden by an inheritence class, use "virtual" instead of "abstract":

public virtual void Use()
{
// Generic instructions for using a tool
}

Be aware that, although all inherited classes still have access to virtual methods, they are not required to override them.

3) You don't have to use only one base class. You could create two additional base classes, PowerTool and HandTool that inherit from Tool, and then have your specific tools inherit from either PowerTool or HandTool. PowerTool would have specific methods that all power tools need, and HandTool would have specific methods that all hand tools need. Think of inheritance as a family tree, where in this case, "Tool" is the trunk, "PowerTool" and "HandTool" are both branches, and "ScrewDriver" and "PowerSaw" are twigs.

4) For the record, the ability to use all inherited tools as a normal tool (ie, using a ScrewDriver as a Tool), is called "polymorphism".

5) There are also some really neat things called "Interfaces", which you may want to look into once you're comfortable with inheritance.

No comments:

Post a Comment