An abstract base class is intended to provide the “scaffolding” for a hierarchy of related classes, but it is not intended to be instantiated itself, because it isn’t “finished.” It requires that classes derived from it add in some missing bits.
Let’s turn our current firefighter into an abstract base for the others to use, and see how that works.
First, we can add the abstract
modifier to the class itself, and see what happens:
abstract class Firefighter { // ... }
As usual, we add the modifier before the class
keyword (and after any accessibility
modifiers, if present).
If we build, we now get a compiler error:
Cannot create an instance of the abstract class or interface 'Firefighter'
That’s because we’re trying to create an instance of the Firefighter
in our main function:
Firefighter joe = new Firefighter { Name = "Joe" };
This is no longer allowed, because the Firefighter
class is now abstract.
OK, we’ll comment that out temporarily while we carry on refactoring. We want it to continue to build as we go so that we can see if we’ve introduced any other errors:
//Firefighter joe = new Firefighter { Name = "Joe" }; //joe.ExtinguishFire;
Build and run, and we get the output we expect—Bill is still spraying the water around:
Bill is putting out the fire! There's water going everywhere! Training the hose on the fire.
One other thing: if we’re creating an abstract base class, we
usually name it something such as FooBase
to distinguish it from a regular class.
This is by no means a hard-and-fast rule, but it is pretty common. So
let’s rename Firefighter
to FirefighterBase
, and make sure we change it
where it is referenced elsewhere—on the Firetruck
, FireChief
, and TraineeFirefighter
classes.
The easiest way to do that is to use the automatic rename refactoring in the IDE. Just type over the old name in the declaration, click on the Smart Tag that appears, and choose Rename Firefighter to FirefighterBase from the menu. You could do it by hand if you wanted, though.
The whole purpose of this was to get rid of the default
implementation we have for putting out fires, so let’s turn Firefighterbase.ExtinguishFire
into an
abstract method.
Just like the modifier for the class, we use the abstract
keyword, but this time we also remove
the method body and add a semicolon at the end of the declaration:
abstract class FirefighterBase { public abstract void ExtinguishFire(); }
If you try building again now, you can see that we have a new compiler error:
'TraineeFirefighter' does not implement inherited abstract member 'FirefighterBase.ExtinguishFire()'
Remember, we are required to override an
abstract
method; our class isn’t
finished until we do so (unlike a virtual
method, where we are invited to override
it, but it will fall back on the base if we don’t). While our FireChief
does override the method, our TraineeFirefighter
doesn’t. So we need to add a
suitable implementation:
class TraineeFirefighter : FirefighterBase
{
// Override the abstract method
public override void ExtinguishFire()
{
// What are we going to put here?
}
// ...
}
But what are we going to put into that ExtinguishFire
override? Before, we depended on
our base class for the implementation, but our base is now abstract, so we
don’t have one available anymore!
That’s because we’ve forgotten about our regular Firefighter
. Let’s add a class for him back into
the hierarchy:
class Firefighter : FirefighterBase
{
public override void ExtinguishFire()
{
Console.WriteLine("{0} is putting out the fire!", Name);
TurnOnHose();
TrainHoseOnFire();
}
}
Notice we’ve given him the “standard firefighter” implementation for
ExtinguishFire
.
If we take one more look at the base class, we can see that we still
have those two virtual
implementation
helpers. While everything builds correctly at the moment, they don’t
really belong there; they are really a part of the Firefighter
implementation, so let’s move them
in there. We end up with the code in Example 4-13.
Example 4-13. Refactored base classes
abstract class FirefighterBase { public abstract void ExtinguishFire(); public string Name { get; set; } public void Drive(Firetruck truckToDrive, Point coordinates) { if (truckToDrive.Driver != this) { // We can't drive the truck if we're not the driver return; } truckToDrive.Drive(coordinates); } } class Firefighter : FirefighterBase { public override void ExtinguishFire() { Console.WriteLine("{0} is putting out the fire!", Name); TrainHoseOnFire(); TurnOnHose(); } protected virtual void TurnOnHose() { Console.WriteLine("The fire is going out.");
} protected virtual void TrainHoseOnFire() { Console.WriteLine("Training the hose on the fire.");
} }
But we’re still not quite done! If you build this you’ll see another compiler error:
'TraineeFirefighter.TurnOnHose()': no suitable method found to override 'TraineeFirefighter.TrainHoseOnFire()': no suitable method found to override
Our trainee firefighter really is a kind of firefighter, and depends
on those two virtual
functions we just moved. The error
message is telling us that we can’t override a method that isn’t actually
present in the base.
We need to change its base class from FirefighterBase
to Firefighter
. This has the advantage that we can
also get rid of its duplicate override of the ExtingushFire
method (see Example 4-14).
Example 4-14. Using the newly refactored base classes
class TraineeFirefighter : Firefighter { protected override void TurnOnHose() { if (hoseTrainedOnFire) { Console.WriteLine("The fire is going out."); } else { Console.WriteLine("There's water going everywhere!"); } } private bool hoseTrainedOnFire; protected override void TrainHoseOnFire() { hoseTrainedOnFire = true; Console.WriteLine("Training the hose on the fire."); } }
We also need to uncomment our two lines about Joe in the Main
function—everything should work
again:
Firefighter joe = new Firefighter { Name = "Joe" }; joe.ExtinguishFire();
We can build and run to check that. We get the expected output:
Joe is putting out the fire! Training the hose on the fire. The fire is going out. Bill is putting out the fire! There's water going everywhere! Training the hose on the fire.
Let’s remind ourselves of the current class hierarchy (see Figure 4-2). Our FireChief
is no longer an “ordinary” Firefighter
, with an override for putting out
fires, but he does take advantage of our common scaffolding for
“firefighters in general” that we modeled as an abstract base class called
FirefighterBase
. Our Firefighter
also takes advantage of that same
scaffolding, but our TraineeFirefighter
really is a Firefighter
—just with its
own idiosyncratic way of doing some of the internal methods that Firefighter
uses to get the job done.
Back to the requirements for our fire department application: let’s say we want to keep track of who is actually in the fire station at any particular time, just in case there is a fire on the premises and we can take a roll call (health and safety is very important, especially in a fire station).
There are two types of folks in the fire station: the firefighters
and the administrators. Example 4-15 shows our new
Administrator
class.
Example 4-15. A class representing administrative staff
class Administrator { public string Title { get; set; } public string Forename { get; set; } public string Surname { get; set; } public stringName
{ get { StringBuildername =
new StringBuilder();
AppendWithSpace(name, Title); AppendWithSpace(name, Forename); AppendWithSpace(name, Surname); return name.ToString(); } } void AppendWithSpace(StringBuilder builder, string stringToAppend) { // Don't do anything if the string is empty if (string.IsNullOrEmpty(stringToAppend)) { return; } // Add a space if we've got any text already if (builder.Length > 0) { builder.Append(" "); } builder.Append(stringToAppend); } }
If you look at our Firefighter
class, it had a single string
property
for a Name
. With the Administrator
, you can independently get and set
the Title
, Forename
, and Surname
. We then provided a special read-only
property that returns a single formatted string for the whole Name
. It uses a framework class called StringBuilder
to assemble the name from the
individual components as efficiently as possible.
AppendWithSpace
is a utility
function that does the actual work of concatenating the substrings. It
works out whether it needs to append anything at all using a static
method on string
that checks whether it is null or empty,
called IsNullOrEmpty
; finally, it adds
an extra space to separate the individual words.
To do the roll call we want to write some code such as that in Example 4-16.
Example 4-16. Using the Administrator class
static void Main(string[] args) { FireStation station = new FireStation(); // A reference to Joe, Harry's number one Firefighter joe = new Firefighter { Name = "Joe" }; // A reference to Bill, the trainee FirefighterBase bill = new TraineeFirefighter { Name = "Bill" }; // Harry is back FireChief bigChiefHarry = new FireChief { Name = "Harry"}; // And here's our administrator - Arthur Administrator arthur = new Administrator { Title = "Mr", Forename = "Arthur", Surname = "Askey" }; station.ClockIn(joe); station.ClockIn(bill); station.ClockIn(bigChiefHarry); station.ClockIn(arthur); station.RollCall(); Console.ReadKey(); }
When you are designing a class framework it can often be a good idea to write some example client code. You can then ensure that your design is a good abstract model while supporting clean, simple code at point-of-use.
Clearly, we’re going to need a FireStation
class that is going to let our
administrators and firefighters ClockIn
(registering their presence in the station), and where we can do a
RollCall
(displaying their names). But
what type is that ClockIn
function
going to take, given that we haven’t specified any common base class that
they share?