Friday 17 July 2009

Aspects of Polymorphism in .NET Part 3 - Abstract Classes

Part Three: Abstraction

All of us are aware of abstract concepts, although perhaps we aren't aware that we're aware. To explain... all of us know that there are things we can touch, possess, and things that we can't. For example, we all eat food, but we never actually have a food. Food is an abstract concept and we actually eat instances of food - apples, hamburgers, pizzas, carrots, etc.

Abstraction is at the top level of most things we're familiar with on a day to day basis. In our Ford Focus illustration we were dealing with a concrete instance of an abstract concept - vehicle. Although a person may be said to own a vehicle, it's meaningless without specifying the type of vehicle he or she possesses. For most of us this is a car, but for some it might be a plane, a boat, a bike, a helicopter etc. We then further solidify things by becoming more and more specific about things - what make, model, variation of car we have, for example.

In programming terms abstraction allows us to define a very loose model for something without specifying exactly how the implementation will be handled. Abstraction comes in varying degrees in the programming world. There are Interfaces, which are completely abstract (contain absolutlely no implementation code), and abstract classes, which can contain a mixture of abstract methods and implementation code. This will all be explained before long, so don't worry if you don't understand these terms just yet.

We'll start by moving one step up the vehicle abstraction hierarchy from our Focus, to the Car to explain abstract classes. Cars come in all different shapes and sizes, but all working cars share certain characteristics, no matter how new, old, cheap or expensive they are. They all start, stop, move, turn etc. How these characteristics are accomplished though, can be different from one car to the next. For example, one manufacturer may start the car by a simple key turn, another by a push button, yet another by fingerprint recognition. On the other hand, though, some things are common between all cars - they are all driven by an engine, stopped by brakes etc. These rules apply for cars, but not for other types of vehice - sailing boats, for example, are driven by the wind and stopped (if in a hurry) by an anchor.

So, if we were to extend our model code for the Focus we would have to say that our Focus is a concrete implementation of a Car (abstract class). Of course, because we're building our code in the order in which wer're covering the topics in this blog, we'll next be building our abstract "Car" class. In pratice, we would design our classes using a modelling tool such as UML, designing interfaces and abstractions up front - the inverse to the way wer'e doing things in this blog - and code concrete classes on the basis of our abstractions. For further reading on the matter Google "Design Patterns" or "Gang of Four".

There is a lot to be said for abstracting functionailty, but I strongly believe that you need understand the bigger picture first.

Let's have a look at how we code an abstract class in C#:

namespace Ford
{
public abstract class Car
{
public abstract void Start(Guid keyCode);
}
}


This is a very basic start, but it demonstrates all that we need to show for now... First of all notice the use of the keyword abstract. This marks the class as being abstract, so instances of this class cannot be created. So,

Car c = new Car();
will result in a compilation error:
Cannot create an instance of the abstract class or interface 'Ford.Car'
Next we have an abstract method:

public abstract void Start(Guid keyCode);


Notice that this method doesn't have a body (i.e. no curly braces).
That's because the method is abstract - its implementation, or how it will start, must
be coded in a class that implements this abstract class. We'll soon see
how this affects the Focus class, which we'll alter to implement the Car class.


However, abstract classes can contain implementation logic too, which is then inherited by the classes that implement it. For example, since in our fairly basic example, we can safely assume that all cars will accelerate by engaging the engine and stop by engaging the brakes, we can promote this logic to the abstract class level. Then all cars will benefit from this standard logic.
Our modified abstract class now looks like this:
   public abstract class Car
{
protected Engine _engine = new Engine();
private double _currentSpeed;
private Brake[] _brakes = new Brake[4] { new Brake(), new Brake(), new Brake(), new Brake() };

public abstract void Start(Guid keyCode);

public void Accelerate(double initialSpeed, double endSpeed)
{
while (_currentSpeed <>
{
_engine.Throttle();
}
_engine.Idle();
}

public void Brake()
{
_brakes[0].Apply();
_brakes[1].Apply();
_brakes[2].Apply();
_brakes[3].Apply();
}

}

Now we'll look at the changes we need to make to our Focus class in order to implement the Car class.

As a reminder, let's look at the code as it was:


public class Focus{
private Engine _engine = new Engine();
private double _currentSpeed;
private int _doorCount = 4;
private Guid _keyCode;

public Focus(Guid keyCode){
//Default constructor
_keyCode = keyCode;
}

public Focus(Guid keyCode, int numDoors) : this(keyCode)
{
_doorCount = numDoors;
}

public void Start(Guid keyCode)
{
if(keyCode == _keyCode)
_engine.Start();
}

public void Accelerate(double initialSpeed, double endSpeed)
{
while(_currentSpeed < endSpeed){
_engine.Throttle();

}
_engine.Idle();
}

public int DoorCount{
get{
return _doorCount;
}
set{
_doorCount = value;
}
}

// ...
// ...

}


In order to implement the new Car abstract class the following changes need to be made:

   
public class Focus : Car
{
private int _doorCount = 4;
private Guid _keyCode;
public Focus(Guid keyCode)
{
//Default constructor
_keyCode = keyCode;
}
public Focus(Guid keyCode, int numDoors)
: this(keyCode)
{
_doorCount = numDoors;
}
public override void Start(Guid keyCode)
{
if (keyCode == _keyCode)
_engine.Start();
}
public int DoorCount
{
get
{
return _doorCount;
}
set
{
_doorCount = value;
}
}
// ...
// ...
}


Notice the changes to the class:
1) We've added : Car to the class declaration. Just as with the extension of Focus into FocusLE, this notifies the compiler that we are going to be implementing the Car abstract class.
2) The Accelerate method has been removed since this logic is now contained in the abstract class.
3) We still have the Start method, although we've had to add the [italic]override[/italic] keyword to the method statement.

Why do we get rid of Accelerate, but keep Start? Because Start is declared as an abstract method in the Car class. In other words it must be implemented in a child class, such as "Focus". On the other hand, Accelerate contains implementation logic within the abstract "Car" class and therefore doesn't need to be overriden in the "Focus" class - although it [italic]could be[/italic] if needed.

Now, when we create an instance of Focus we get access to the Accelerate and Brake methods. Let's create a new class, called "Driver" and demonstrate this inheritance:

    public class Driver
{
public void Init()
{
Guid _keyCode = new Guid();
Focus myFocus = new Focus(_keyCode);
myFocus.Start(_keyCode);
myFocus.Accelerate(0, 50);
myFocus.Brake();

}
}
The code highlighted blue demonstrates the fact that the "myFocus" object (i.e. the instance of the "Focus" class) can Accelerate and Brake even though "Focus" doesn't define these methods.

Why?

You may be wondering - "why is any of this useful?" Well, now that we've abstracted things out to the level of "Car" we can deal at that abstract level. Say for example, we were creating a class called "Garage". We can now easily build that "Garage" class around Cars rather than Focuses.

    public class Garage
{
public void Service(Car aCar)
{
//...
//Code to Service the care
//...
}
}
Because every "Focus" is also a "Car" (by virtue of the fact that it has implemented the "Car" abstract class) this, and any other class that implements the "Car" abstract class can now be serviced at the "Garage".

Once you understand the principles of abstraction it doesn't take long to realise the massive potential for it to make your code more flexible and extensible.

Summary

This tutorial has demonstrated what abstract classes are, and by the time you've finished reading this series of blogs on polymorphism I hope you will feel sufficiently equipped to go on to further reading, such as books on Design Patterns. These books will help you to get a stronger grasp on the far reaching benefits of abstraction.

The next and final part of this blog series will deal with interfaces.

No comments:

Post a Comment