Click here to Skip to main content
15,878,852 members
Articles / .NET

Aspects of Polymorphism in .NET Part 3 - Abstract Classes

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
3 Aug 2009CPOL6 min read 12.7K   12  
Aspects of polymorphism in .NET - 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 absolutely 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 vehicle - 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 we're covering the topics in this blog, we'll next be building our abstract "Car" class. In practice, we would design our classes using a modelling tool such as UML, designing interfaces and abstractions up front - the inverse to the way we're 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 functionality, 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#:

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,

C#
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:

C#
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:

C#
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:

C#
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:

C#
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 overridden 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:

C#
    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.

C#
    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 realize 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer The Test Factory
United Kingdom United Kingdom
Experienced web and software developer who has worked for some of the best and most respected software companies in the North of England as well as spending several years self employed.

Extensive knowledge of desktop and web programming languages, particularly C#, VB.NET, JavaScript, XML, CSS, Web Services, ASP.NET.

Comments and Discussions

 
-- There are no messages in this forum --