|
by kirupa |
5 January
2007
Let's say I want to create a game populated by different
characters. All of the characters will share certain
characteristics, but some characters will have features
unique only to them. In this world, the characters I want
are Aliens, Bandits, Cowboys, and Pirates, and the following
diagram shows what each are capable of:

If you were to write a program that allowed all four of
the above characters to exist, you can do so. You can have
an Alien class, a Bandit class, a Cowboy class, and a Pirate
class. Since you know what each character is capable of,
your class will contain the appropriate methods for walking,
talking, saying, teleporting, running, etc. This approach is
fine, but is there a better way of writing these four
characters?
For example, all of the characters seem to share a Walk,
Talk, and Say action. Doesn't it seem a bit wasteful to code
a Walk, Talk, and Say method for each character separately?
This is where inheritance comes in.
Inheritance is an important part of OOP because it allows
you to build bigger blocks by reusing existing smaller
blocks. To look at it in terms of what we are trying to do,
instead of having four classes with each having the same
implementation of Walk, Talk, and Say, how about you we
create a separate Character class with the basic
functionality and work from there:

By placing the shared Walk, Talk, and Say components in a
Character class and having my characters build on top of it,
I am able to do several things:
- I make the code more modular by dividing larger
pieces of code into smaller, more maintainable ones.
- I introduce a hierarchy where objects become more
specialized as you move down the tree. A Character is
pretty generic and can just walk talk and say something,
but an Alien character does all that plus has the
ability to teleport and hide.
- I can now reuse code. Instead of coding Walk, Talk,
and Say for each of my characters, I only code it them
only once for my Character class.
There are other features, but the above three are what I
think are the most important ones. So, now that you have a
brief idea of what inheritance is and why it is useful,
let's look at some code that brings the above example to
life.
Below, I have provided the code for the Character class
as well as a Program class that contains our Main method:
- class
Character
- {
- public
void
Walk()
- {
- Console.WriteLine("Character
walking!");
- }
-
- public
void
Talk()
- {
- Console.WriteLine("Character
is talking about something");
- }
-
- public
void
Say(string
thingToSay)
- {
- Console.WriteLine("Character
says: {0}",
thingToSay);
- }
-
- }
-
- class
Program
- {
- static
void
Main(string[]
args)
- {
- Character
foo
=
new
Character();
- foo.Say("Hello
World!");
- }
- }
The above code is very straightforward, and any
interesting things you see in the above code is largely
covered by my earlier
Classes tutorial. If you run the program, you will see
Character says: Hello World! displayed on
the screen.
If you look at the AutoComplete for the foo object, you
should see the following:

Notice that you can access not only the default Object
methods Equals,
GetHashCode,
GetType, and
ToString, but of course also
the Say,
Talk, and
Walk methods you wrote
yourself.
At this point, we just have our Character class setup. What
we want to do is use our Character class to build our Alien,
Bandit, Cowboy, and Pirate characters. Basing one class on
another is often called extending the class. For example,
because our Alien class will be based on our Character
class, we can say that Alien extends Character.
The following is our code for the Alien class:
- class
Character
- {
- public
void
Walk()
- {
- Console.WriteLine("Character
walking!");
- }
-
- public
void
Talk()
- {
- Console.WriteLine("Character
is talking about something");
- }
-
- public
void
Say(string
thingToSay)
- {
- Console.WriteLine("Character
says: {0}", thingToSay);
- }
- }
-
- class
Alien
: Character
- {
- public
void
Teleport(string
currentLocation,
string
newLocation)
- {
- Console.WriteLine("The
alien teleported from {0} to {1}",
currentLocation,
newLocation);
- }
-
- public
void
Hide()
- {
- Console.WriteLine("The
alien is hiding.");
- }
- }
-
- class
Program
- {
- static
void
Main(string[]
args)
- {
- Character
foo
=
new
Character();
- foo.Say("Hello
World!");
- }
- }
The Alien class looks like any other class definition,
but there is one major difference. Notice the
: Character after the Alien
text in the class declaration. The : (colon) is shorthand
for extends, as in, this class will be extended by
Character.
Our Alien class itself only contains the Teleport and
Hide methods, but because we extended the Character class,
you also have access to the
methods defined in the Character class:

This means that your Alien can not only teleport and
hide, but it can also say, talk, and walk like a normal
Character. Best of all, you only had to write your own
teleport and hide functionality.
When extending a class, you will run into situations where
the functionality provided by your base class (Character in
our case) is not exactly what you want. For example, if you
tell your alien to say "X-Files is my favorite show" , you
will see something like this: Character says,
X-Files is my favorite show.
Let's say you want to modify what your alien says. You
could just go and edit the Say method in your Character
class, and the end result will be that your alien will now
use the modified Say method. There is a problem with this
approach though. Because you modified the base Character
class, any modification you make to your Character class
will also propagate to classes that extend it. For example,
your Bandit, Cowboy, and Pirate classes will now inherit
your modified Say method also, and you might not want that.
You can avoid this problem by either hiding
the unwanted method or by overriding
the old method with a new method. Let's set both of those
scenarios up by simply adding a new Say method inside the
Alien class:
- class
Alien
: Character
- {
- public
void
Teleport(string
currentLocation,
string
newLocation)
- {
- Console.WriteLine("The
alien teleported from {0} to {1}",
currentLocation,
newLocation);
- }
-
- public
void
Hide()
- {
- Console.WriteLine("The
alien is hiding.");
- }
-
- public
void
Say(string
thingToSay)
- {
- for
(int
i
=
0;
i
<
5;
i++)
- {
- Console.WriteLine("Alien
says: {0}",
thingToSay);
- }
- }
- }
-
- class
Program
- {
- static
void
Main(string[]
args)
- {
- Character
foo
=
new
Character();
- foo.Say("Hello
World!");
-
- Alien
zorb
=
new
Alien();
- zorb.Say("Take
me to your leader!");
- }
- }
If you run your program, look a the grayed out Program
class for an example, your Alien object will repeat what you
tell it to say five times. What you have done is not really
override your Say method. You simply hid the Say method from
your Character class, and your compiler will throw a warning
if you attempt to do something like the following:
- class
Program
- {
- static
void
Main(string[]
args)
- {
- Character
zorb
=
new
Alien();
- zorb.Say("Take
me to your leader!");
- }
- }
In the above case, you are being ambiguous on which Say
method to call. Will your zorb object be calling the
Character's Say object or the Alien's Say object? Your
compiler will even warn you of this.
In the above example, you will actually call the
Character's Say method. To be less ambiguous, you can hide
methods by using the new keyword instead:
- class
Alien :
Character
- {
- public
void
Teleport(string
currentLocation, string
newLocation)
- {
- Console.WriteLine("The
alien teleported from {0} to {1}",
currentLocation, newLocation);
- }
-
- public
void
Hide()
- {
- Console.WriteLine("The
alien is hiding.");
- }
-
- public
new
void
Say(string
thingToSay)
- {
- for
(int
i
=
0;
i
<
5;
i++)
- {
- Console.WriteLine("Alien
says: {0}",
thingToSay);
- }
- }
- }
When you add the new keyword to your Say method, you
explicitly tell your compiler that the Alien's Say method is
intended hide the Say method from the base (Character)
class. This is actually the default behavior, so the earlier
example without the new keyword worked fine if you can
ignore the compiler warning.
Overriding a method is a little different. When you
override a method, you tell the compiler to uses the
overridden method if possible. In our above example, if we
overrode the Say method in our Alien class, our earlier
example will use the Say method in the Alien class instead.
Overriding a method takes two steps. You first declare
the method you choose the override as virtual. In our
example, we declare our Character class's Say method as
virtual:
- class
Character
- {
- public
void
Walk()
- {
- Console.WriteLine("Character
walking!");
- }
-
- public
void
Talk()
- {
- Console.WriteLine("Character
is talking about something");
- }
-
- public
virtual
void
Say(string
thingToSay)
- {
- Console.WriteLine("Character
says: {0}",
thingToSay);
- }
- }
In the second step, we declare our overriding method in
our child class with the override modifier. In our example,
the Alien class's Say method will be marked for override:
- class
Alien :
Character
- {
- public
void
Teleport(string
currentLocation, string
newLocation)
- {
- Console.WriteLine("The
alien teleported from {0} to {1}",
currentLocation, newLocation);
- }
-
- public
void
Hide()
- {
- Console.WriteLine("The
alien is hiding.");
- }
-
- public
override
void
Say(string
thingToSay)
- {
- for
(int
i
=
0;
i
<
5;
i++)
- {
- Console.WriteLine("Alien
says: {0}",
thingToSay);
- }
- }
- }
If you run our earlier program,
zorb.Say("Take me to your leader") will now access
your Alien class's Say method.
When creating your class, unless you believe that a
method in it will be overridden, it is best to not leave
them declared as virtual. There is a slight performance hit
when declaring methods unnecessarily as virtual.
In the end, the main reasons for new and override to be
used is when you have no control over the base class. In
many projects, you will be using a base implementation
created by another developer or another company. You want to
ensures that, if the other developer or company releases and
update to the base class, your extended child classes will
still work well.
As you can see, inheritance is a very important part of
writing software. In software development, you are often
told the virtues of having small pieces of reusable code to
make writing and maintaining programs easier. An important
concept in writing modular code is inheritance.
In this article, you learned how to take a very basic
class called Character and gave it some basic functionality.
You extended the basic functionality by creating more
specialized characters such as our Alien who can both
perform everything a character can, but also, perform some
unique tricks a character cannot do.
|