Having got through all of that, you’re probably rightly concerned that, simple though it is in theory, the practical implications of inheritance are actually rather complex and require a lot of documentation, testing, and imagining how people might use and abuse your virtual methods. And we have to do that for every class down the hierarchy.
When we designed our FireChief
,
we happily provided an override for the ExtinguishFire
method, without giving a thought
for the fact that someone else might override that method in his
own derived class. In fact, we didn’t even consider the
possibility that anyone might derive from FireChief
at all. No documentation,
nothing.
Now there are several members on our own base class that could be
overridden by a class that derives from FireChief
. Does that have any implications for
our own documentation or testing? Can we even tell? And how could we have
guessed that was going to happen when we built our FireChief
class, since there was only one
virtual member on the base at that time? This looks like it has the
potential to become a rich future source of bugs and security
holes.
Fortunately, we can eliminate this problem at a stroke by saying
that we didn’t design our FireChief
to be derived from, and stopping
anyone from doing so. We do that by marking the FireChief
class sealed
. Let’s see how
that looks:
sealed
class FireChief : Firefighter
{
// ...
}
We apply the sealed
modifier
before the class
keyword and after any
accessibility modifiers if they are present.
So, what happens if we try to derive a new class from FireChief
now?
class MasterChief : FireChief { }
Compile it, and you’ll see the following error:
'MasterChief': cannot derive from sealed type 'FireChief'
That’s put a stop to that. Let’s delete our MasterChief
so that everything builds
again.
Not only can sealing classes be very useful (and defensive), but if you decide later that you want to unseal the class and allow people to derive their own types, it doesn’t (normally) break binary compatibility for the type. Sealing a previously unsealed class, however, does break compatibility.
We now have three different types of firefighter. Let’s remind ourselves how they are related (see Figure 4-1).
Those three types of firefighter basically differ in the strategy that they use for putting out fires. There’s a base class that provides a default implementation, and a couple of classes that override the virtual methods to do things differently.
Let’s say we wanted to support lots of different types of firefighter, all of whom were expected to have a different approach to fighting fire, from the trainee, to the chief, to Gulliver (who has his own idiosyncratic way of putting out a fire in Lilliput).
We still want the handy Name
property and the Drive
method, and we
still want anyone to be able to call an ExtinguishFire
method.
Noticing that our FireChief
, for
example, doesn’t make use of the base implementation at all; we don’t want
to provide a standard for that method. We’ll just let all implementers
decide for themselves how it is going to work.
We’re shooting for something that looks like Figure 4-2.