Where is My Inheritance?
In this chapter you will learn how the fictional character Phaedrus explains
the complex subject of a class hierarchy in "Zen and the Art of Motorcycle
Maintenance", Robert M. Pirsig, Quill, 1974, 418 pp. You will also
learn the art of "black box programming" with abstract classes and
abstract
methods.
Lets Get Abstract
One of the major advantages of OOP is that is simplifies the task of creating
and maintaining complex applications. The road to this simple life is through "abstraction".
Abstraction is the ability to "decompose" a complex problem or
structure into simpler problems or pieces. In abstraction, you try to find
the "essence" of a thing or idea. You break down a complex problem
into smaller, more manageable chunks. Abstraction is what the character Phaedrus
describes in Chapter 8 in "Zen and the Art of Motorcycle Maintenance."
"John looks at the motorcycle and he sees steel in various shapes
and has negative feelings about theses steel shapes and turns off the whole
thing.
I look at the shapes of the steel now and I see ideas. He thinks I am working
on parts. I'm working on concepts."
Phaedrus then proceeds to divide up the concept of a motorcycle into components
and functions. Then he divides the components into a power assembly and a running
assembly. To which he concludes:
"Finally you see that while I was splitting the cycle up into finer
and finer pieces, I was also building a structure. This structure of concepts
is
formally called a hierarchy and since ancient times has been a basic structure
for all Western knowledge.... That's all the motorcycle is, a system of concepts
worked out in steel."
Thus the fine art of motorcycle maintenance is based on abstraction, decomposing
the complex concept of the motorcycle into a hierarchy of smaller simpler concepts.
The fine art of OOP is also based on abstraction. You decompose the complex
application domain into a hierarchy of smaller pieces that make the application
easier to create and maintain. Now I am not saying that Phaedrus was a great
object oriented programmer, but he clearly understood the importance of critically
examining the problem at hand and dividing up the problem into a hierarchy
of ideas.
Let's look at Phaedrus' motorcycle in C#. First, a motorcycle is a vehicle
since it can carry a payload. Second, it is motorized since it has a motor
and consumes fuel. The "essence" of a motorcycle is that of a "vehicle" that
is "motorized". Of course a motorcycle differs from a a car, an airplane,
or a boat. Motorized vehicles can be ridden, driven, flown or piloted. Land
vehicles can have two wheels or four wheels. What we have here is a hierarchy
of ideas. We go from a most general concept to a more specific or "specialized" concept.
Just as Phaedrus' abstraction of the motorcycle made him a better mechanic,
your abstraction of a complex application, makes you a better and more efficient
programmer. You do not think of a program as concrete parts (global data and
methods). You think of a program as a hierarchy of ideas. Welcome to "Zen
and the Art of Application Maintenance."
Inheritance Is the Means to Abstraction
Let's face it. Just creating a hierarchy of ideas is not enough. The language
must provide a mechanism to support the abstraction hierarchy. That mechanism
is called inheritance. Inheritance allows programmers to modify or extend
(or inherit) the properties and behavior (methods) of a class. It promotes "code
reuse", the holy grail of programming. Throughout the .NET framework,
you see the use of abstraction and class hierarchies.
Inheritance in the .NET Framework
All classes in the .NET framework derive from a single base class called Object.
The Object class is the "root of the type hierarchy." Even the
built in value types are aliases to classes that derive from Object, so that
int actually maps to System.Int32, which derives from Object. As a result,
you can call the Object class methods Equals, Finalize, GetHashCode and ToString
on any class in the .NET framework. This makes it very easy to debug an application
using console output since any .NET class will implement the ToString() method.
An example of inheritance in the ASP.NET framework is a TextBox. Here is the
class hierarchy for System.Web.UI.WebControls.TextBox:
System.Object
System.Web.UI.Control
System.Web.UI.WebControls.WebControl
System.Web.UI.WebControls.TextBox
The TextBox control inherits from WebControl which inherits from Control which
inherits from Object. Wow. And I thought my family tree was complicated.
Getting Your Inheritance
In the following section you, a twisted programmer, build a "SpaceToaster" class
in C# that inherits from the Toaster class.
Disclaimer: This is a very twisted tutorial. Reading this online book may
be hazardous to your mental health.
You have been contracted by a famous "Boy Genius" movie cartoon
character to extend the common household toaster for use as a orbiting satellite
transmitter. SpaceToaster is a specialization of a Toaster that extends the
behavior of the lowly toaster into a satellite.
Note: For those of you who don't know what cartoon movie I am talking about,
either you have no children or you need to get out more. Here's a hint: "Jimmy
Neutron Boy Genius", Paramount Movies, Nickelodeon Movies, 2001.
First you need to delete the Main method of the Toaster3 class since an executable
can only have one entry point. You can now create a new class "SpaceToaster" that
inherits all of the code in the Toaster3 class and has a Main method:
class SpaceToaster : Toaster3
{
[STAThread]
static void Main(string[] args)
{
}
}
This is not a very useful class! The syntax for inheritance is
class ChildClass : ParentClass {}
Note: Toaster3 is the parent class and SpaceToaster is the child class. You
could also say the the child class is a "subclass" or "derived
class" and that the parent class is the "superclass" or "base
class." The child inherits the knowledge (methods and variables) of the
parent class, but can only "touch" the public (and protected) methods
and variables of the parent. Sort of reminds me of a trust account. The child's
ability to "touch" the assets of the trust is limited.
Next, you can add a new behavior to the derived class extending the functionality
of the lowly Toaster:
class SpaceToaster : Toaster3
{
[STAThread]
static void Main(string[] args)
{
SpaceToaster st= new SpaceToaster();
Console.WriteLine(st.Broadcast());
Console.ReadLine();
}
public string Broadcast() // <-- new behavior
{
return "Hello From Earth!";
}
}
Which amazingly outputs: "Hello From Earth!"
Since SpaceToaster also inherits the methods and properties of Toaster3, it
can still make toast while orbiting planet Earth:
class SpaceToaster : Toaster3
{
[STAThread]
static void Main(string[] args)
{
SpaceToaster st= new SpaceToaster();
Console.WriteLine(st.Broadcast());
st.SecondsToToast= 10;
Console.WriteLine(st.MakeToast());
Console.ReadLine();
}
public string Broadcast()
{
return "Hello From Earth!";
}
}
Inheritance vs. Containment
One difficult design decision is to decide if a class should inherit from
a parent class or hold a reference to a new object. Inheritance represents
an
IS_A relationship from a generalization to a specialization. Containment
represents a HAS_A relationship between the whole and a part. So a car
IS_A motorized
vehicle, but HAS_A radio. The two relationships can be expressed in code
thusly:
class Radio
{
...
}
class Vehicle
{
...
}
class Car : Vehicle
{
Radio r= new Radio();
}
The essence of a car is that it is a vehicle. A radio is not essential to
a car. Instead a radio is part of a car. That said; let me acknowledge
that none
of my teenage children agree with me. They are adamant that a radio with
a CD player _is_ essential to a car. So a teenage design looks more like:
class TeenageVehicle : Radio
{
...
}
Public, Protected and Private
Heck. I forgot that I had promised in Chapter 1 to explain the access modifier "protected".
In a nutshell, a private method or variable is only touchable within the class
that declared the variable. A public method or variable is touchable from outside
the class. A protected method or variable lies between public and private.
Protected methods or variables are only touchable (visible) from within the
declaring class _or_ from a class that inherits from the declaring class. So
declaring a variable protected allows a designer to access your base class
variable inside of their derived class. Suffice it to say that there has been
a lot of discussion about the use of private versus protected in class design.
Make It Abstract
Is your head spinning yet? If so, I apologize, but I need to introduce one
more subject that is related to abstraction, the "abstract class".
An abstract class defines zero or more abstract methods. An abstract method
defines the signature of the method, but not the body of the method. You
cannot create an instance of an abstract class.
Note: Similar in concept to an abstract class is the concept of an "interface".
However, an interface contains no implementation, only abstract methods, and
is similar to a pure virtual class in C++.
Nothing helps explain a new programming idiom like sample code. Here is an
abstract C# class "SpaceToaster" that defines an empty abstract method "BroadcastID()":
abstract class SpaceToaster : Toaster3
{
public string Broadcast()
{
return "Hello From Earth!";
}
abstract public string BroadcastID(); // <-- No body or implementation!
}
If you try to create an instance of this abstract class, the compiler will
complain:
Cannot create an instance of the abstract class or interface 'JAL.SpaceToaster'
An abstract class is useful in at least four distinct situations.
Defer the Implementation to Another More Knowledgeable Class
First, an abstract class is useful when you know what the class should do,
but you don't know how to implement the total solution. In this idiom,
you defer the actual implementation or body of one or more methods to another
class. A good example of deferred implementation is a sorting class, say
a quicksort. You may know how to do a quicksort, but you cannot know the
ordinality or ranking of every possible object that needs to be sorted.
You
may want to write a complex generic sort routine and declare the class
abstract with an abstract method Compare(object1, object2). You can even
call this
abstract Compare method in your Sort class (cool!). Now another programmer
with a custom object that needs to be sorted, can inherit from your Sort
class and implement the Compare method with code that defines the ordinality
of the custom object, reusing your generic quicksort algorithm! In a sense,
the child class inherits the incomplete implementation of your Sort class.
Note: C# supports single inheritance of implementation and multiple inheritance
of interfaces. In C#, see the IComparable interface.
Formally Hide or Encapsulate the Implementation for Flexibility
Alternatively, you may just want to formally hide or encapsulate the actual
implementation of a method. By requiring clients to program to the public
view, the abstract method, rather then to the actual implementation, you
promote information hiding or encapsulation. This allows you to change
the implementation in a future release, without affecting any client calls.
The key concept here is that the implementation can change without affecting
a call to your abstract method. This is yet another example of how OOP makes
it easier to maintain your code. If you define an abstract method GetData(),
the data could come from a database query. To the caller of the abstract method,
GetData() is just a black box that returns data. If you later decide to change
the concrete implementation of GetData() from a database solution to a text
file solution, you can recompile the concrete implementation of GetData() without
recompiling classes that call the abstract method GetData().
More interestingly, you could compile an updated class with a different name
and load the updated class at runtime (perhaps passing the name of the new
class in the Main method). As long as the updated class derives from the same
abstract class and as long as the client makes calls through the abstract class,
there is no need to recompile the client application. Cool!
Allow Runtime Variation in Implementation
This concept is difficult to grasp at first, but is an extremely powerful
idiom. In this idiom, one or more concrete classes inherit from an abstract
class,
but each child class may provide a different implementation of the the
abstract class method or methods. The client does not know what concrete
class is
going to be requested at runtime, but simply invokes the public view of
the method. The actual runtime behavior will depend on which concrete classes
is requested at runtime. Thus the method can have more than one form at
runtime,
which is referred to as polymorphism, to have more than one form.
The classic example is an abstract class Drawable with an abstract method
DrawYourself(). Different concrete classes of Drawable items (such as circle
or square) implement the DrawYourself() method in a unique manner and inherit
from Drawable. You can now create a Hashtable of non-null Drawable objects
and iterate over the collection calling someObjectInHashtable.DrawYourself().
Each concrete object in the collection is guaranteed to implement the DrawYourself()
method.
Note: Polymorphism implies late binding of a method name to a concrete implementation.
In some languages, this magic is done with a v_table, which stores an offset
pointer to the actual concrete implementation. OK. That was probably more than
you wanted to know.
Mark Thyself
Finally, the abstract class can simply be a marker, allowing you to identify
all classes that are based on your empty abstract class like this:
abstract class MyMarker {} // <-- no abstract methods
class Child : MyMarker
{
static void Main(string[] args)
{
Child c= new Child();
if (c is MyMarker)
{
System.Console.WriteLine("Is mine.");
}
System.Console.ReadLine();
}
}
Outputs: "Is mine."
Sample C# Code
We finish this chapter with a bit of sample code that demonstrates the syntax
of inheritance from an abstract class. Here again is the abstract version
of the SpaceToaster class:
abstract class SpaceToaster : Toaster3
{
public string Broadcast()
{
return "Hello From Earth!";
}
abstract public string BroadcastID(); // <-- No body or implementation!
}
This abstract class defines the public outside view or signature of the method "BroadcastID".
The method is declared with a return type "string" and an empty parameter
list. The declaration says nothing of how the method is implemented.
Any concrete class that inherits from this abstract class, must provide a
body or implementation for the function BroadcastID(). Here is an concrete
version of a class that inherits from the SpaceToaster class and provides an
implementation of the abstract method BroadcastID(). Note the use of the keyword "override" which
tells the compiler that you are explicitly implementing the abstract method
BroadcastID in your concrete class.
class TwistedSpaceToaster : SpaceToaster
{
[STAThread]
static void Main(string[] args)
{
TwistedSpaceToaster tst= new TwistedSpaceToaster();
Console.WriteLine(tst.BroadcastID());
Console.ReadLine();
}
public override string BroadcastID() // <-- concrete implementation
{
return "Manufactured By Twisted.";
}
}
Running this code outputs: "Manufactured By Twisted."
Congratulations at making it this far. All of this theory, however, is giving
me a headache. Lets move on to some real world techniques for writing better
programs. In the next twisted chapter, we learn a basic design pattern for
writing reusable code, the Model-View/Controller pattern.