OO1 Object Pascal
OO1 Object Pascal
inheritance
Main concepts
Chapter contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Example 6.1 A polymorphic program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Ex 6.1 step 1 Creating the classes and types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Ex 6.1 step 2 Testing the classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Ex 6.1 step 3 Statically bound subclass methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Ex 6.1 step 4 Dynamic binding and polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Ex 6.1 step 5 UML class and object diagrams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Example 6.2 Alternatives to polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 1
Ex 6.2 step 1 No substitution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Ex 6.2 step 2 Substitution without polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Summary of early and late binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Example 6.3 Evolution in a polymorphic program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Ex 6.3 step 1 The additional subclasses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Ex 6.3 step 2 Calling additional subclasses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Example 6.4 Abstract methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Ex 6.4 step 1 Creating an abstract method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Ex 6.4 step 2 Testing the MyShape classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Ex 6.4 step 3 A helper method in the superclass . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Assigning references . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Pattern 6.1 Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Difference between subclassing and subtyping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Chapter 6 summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Introduction
The form of inheritance we concentrated on in the first four chapters is class inheritance, or
subclassing. Subclassing is a very important part of OO programming since it provides a
powerful mechanism for reuse: subclasses reuse their superclasses data fields and methods
through inheritance.
In chapter 5 we began using inheritance differently, for substitution. An important part
of substitution is that the superclass establishes a particular type. Subclasses that implement
this type fully, ie that can substitute for the superclass under all conditions, are called
subtypes. This may not yet be particularly clear since the example in the previous chapter,
which used VCL components, concentrated on substitution. So in this chapter we look
briefly at substitution again, but in the context of programmer-generated classes. We then
incorporate dynamic binding to extend substitution to the concept of polymorphism. This
leads naturally to an exploration of abstract classes. The concepts of substitution, poly-
morphism and abstract classes are closely tied to the concept of subtyping and all three of
these are important aspects of many OO patterns. For some people, polymorphism
represents the heart of OO programming.
This chapter establishes the concept of polymorphism (subtyping) as distinct from reuse
(subclassing). Following chapters illustrate some of the ways in which polymorphism is
We start this chapter by reviewing briefly the principles from the previous chapter (ie
generalisation and substitution), but using programmer defined classes, and then extend
these to a polymorphic program by introducing dynamic binding.
We use a simple ancestor class, TFurniture, and two subclasses, TChair and TTable.
(TChair and TTable are also valid subtypes of the TFurniture type.) We will create objects of
different types and then send a message to each by calling a method that each type responds
too. To keep the principles clear, this is a very simple example, and so these various
furniture objects know only what kind they are. In a full system, say for an antique shop,
they would be more complex, with a variety of methods and carrying information about
their value, what wood they are made of, when they were made, and so on. But our simple
GetKind access method is sufficient to introduce polymorphism.
The driver program has the following interface (figures 1 & 2).
Figure 2 Components
com prising the user interface
In this example well also introduce a convenient tool, called Class Completion (mentioned
briefly in chapter 4), that speeds the creation of our own classes. So this example will cover a
lot of interesting ground!
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 3
Ex 6.1 step 1 Creating the classes and types
Start a new application and add a second unit to it through the menu sequence File | New |
Unit. Save this unit (called Unit2) as FurnitureU.pas and then enter the program as follows.
Start with just the class declarations (lines 311 below). Then place the cursor on the
declaration of function GetKind ... (line 6) and press <Shift+Ctrl+C> (or right-click and select
Complete Class at Cursor) to invoke Class Completion. This will create a skeleton for the
method. Now it is simply a matter of entering the program code (line 16).
1 unit FurnitureU;
2 interface
3 type
4 TFurniture = class (TObject)
5 public
6 function GetKind: string;
7 end; // TFurniture = class (TObject)
12 implementation
13 { TFurniture }
This is a rather strange set of classes! We have three classes. TFurniture, derived from
TObject (line 4), has one method, GetKind, that returns a string. The second and third
classes, TChair and TTable, are derived from TFurniture and have neither data nor methods
at this stage.
Well expand these classes in a little while, but for now we need a driver to test these
class definitions.
Well use the Form and Unit1 to write a driver program (figures 1 & 2). To add the
RadioGroupBoxs Items.Strings, select the RadioGroupBox and click on the three dots
alongside the Items property in the Object Inspector. The property editor appears. Enter
Furniture, Chair, Table, each on a new line.
Notice that we add a private data field of type TFurniture to our user interface class (ie to
the form) in line 16 below and so we must add FurnitureU to the uses clause (line 5).
1 unit SubstitutionU;
2 interface
3 uses
4 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
5 Dialogs, StdCtrls, ExtCtrls, FurnitureU;
6 type
7 TfrmSubstitution = class(TForm)
8 btnKind: TButton;
9 lblKind: TLabel;
10 btnFree: TButton;
11 rgbFurniture: TRadioGroup;
12 procedure btnKindClick(Sender: TObject);
13 procedure btnFreeClick(Sender: TObject);
14 procedure rgbFurnitureClick(Sender: TObject);
15 private
16 MyFurniture: TFurniture; // Declaring an application object
17 end; // TfrmPolymorphism = class(TForm)
18 var
19 frmSubstitution: TfrmSubstitution;
20 implementation
21 {$R *.DFM}
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 5
32 procedure TfrmSubstitution.btnKindClick(Sender: TObject);
33 begin
34 if MyFurniture = nil then
35 lblKind.Caption := 'Object not defined'
36 else
37 lblKind.Caption := MyFurniture.GetKind;
38 end; // procedure TfrmSubstitution.btnTypeClick
Lines 27 and 28 are interesting. MyFurniture is declared as a TFurniture (line 16) and so it is
no surprise that we can assign it to an instance of TFurniture in line 26. But in lines 27 and
28 we assign a reference of type TFurniture to instances of types TChair and TTable. We can
do this because TChair and TTable are derived from TFurniture and so we can take
advantage of substitution.
Run this program and test it. Clicking any of the RadioButtons and then the Kind button
displays the message Furniture. We see the same message whether MyFurniture is a
TFurniture, a TChair or a TTable since line 37 above invokes the GetKind method defined in
TFurniture (step 1 lines 1417) through the inheritance relationships between the classes.
But wouldnt it be nice if the TChair instance could declare itself to be a chair and the
TTable instance show that it is a table? We do this in the next step. But just before doing
that, notice the care taken in this program about creating and freeing the object and testing
for its existence. Since an object continues to exist after the event handler rgbFurnitureClick
completes, the other two event handlers both start with a test for its existence.
We want TChair and TTable instances to be able to respond individually about what kind of
furniture they are, so lets modify the class definitions to give them their own methods.
(Remember to use Code Completion. First type in only the additional method declarations,
lines 910, 1314 below. Placing the cursor on these declarations in turn, press
1 unit FurnitureU;
2 interface
3 type
4 TFurniture = class (TObject)
5 public
6 function GetKind: string;
7 end; // TFurniture = class (TObject)
16 implementation
17 { TFurniture }
22 { TChair }
27 { TTable }
Run and test this. The result is a bit disappointing! No matter whether we click Furniture,
Chair or Table we always get the same Furniture message from TFurnitures GetKind
method. So even if the subclasses have their own methods, when they are assigned to the
superclasss variable type, they invoke the superclasss method and not their own.
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 7
Lets look at this again in terms of the program code. When the compiler reaches line 37
of step 2, the compiler has no way of knowing whether MyFurniture is of type TFurniture,
TChair or TTable. The only clue it has is that in step 2 line 16 we declared the Furniture
variable to be of type TFurniture. So when Delphi compiles step 2 line 37, it links the
method calls that variable MyFurniture makes to the class TFurniture, irrespective of what
class MyFurniture has been assigned to, because this is how the variable Furniture has been
declared. This is called early, static or compile-time binding between the variable and the
method.
However, this is not what we want. We want Delphi to associate the methods with the
class of the particular object we are using at that moment in the execution. If we are using
the superclass because the most recent execution of the Case statement caused the execution
of step 2 line 26, we want Delphi to call the superclasss method when it gets to step 2 line 37
However, if we are using a subclass, (ie if step 2 lines 27 or 28 were most recently executed),
we want Delphi to be smart enough to call the appropriate subclasss method in step 2 line
37. As the next step shows, we can achieve this by using late, dynamic or run-time binding.
(All three terms refer to the same thing.)
We want the subclass methods to be able to override the superclass methods when we are
dealing with the subclass. To do this, the superclass must have virtual methods (ie it must
use dynamic binding). Virtual methods are overridden by subclass methods whenever a
subclass is substituting for the superclass. Doing this is surprisingly easy. We need only
change the method declarations to state that the superclass method is a virtual method (ie
capable of being overridden) and that the subclass methods are override methods (lines 6,
10 & 14 below). We do not need to change the actual method implementations.
3 type
4 TFurniture = class (TObject)
5 public
6 function GetKind: string; virtual;
7 end; // TFurniture = class (TObject)
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 9
However, because of substitution and dynamic binding, a TChair object or a TTable object
can take on the role of a TFurniture object. So the possible associations are between
frmSubstitution (the user interface object) and an instance of any of TFurniture, TChair or
TTable. If, for example, we select Chair on the user interface (and so execute line 27 of step
2), we get the following object diagram (figure 4):
In writing this program without substitution, the only benefit we gain from the inheritance
hierarchy is that TFurniture defines a signature, ie the GetKind method, with which TChair
and TTable comply.
Considering that the previous step was not too successful, can we solve the problem using
substitution but not dynamic binding? To do this, remove the dynamic binding in unit
FurnitureU (ie revert from the code in example 6.1 step 4 to the code in step 3 by
commenting out or removing the virtual and override keywords). Now replace the
polymorphic message (example 6.1, step 2, line 37) with a complex If statement to test the
class of the current MyFurniture instance and then call the required GetKind method (lines
3440 below).
What was accomplished in a single line with polymorphism (step 2 line 37) now requires a
complex set of if statements with static binding. We must test explicitly for each possible
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 11
instance type (using RTTI information through the is operator1 as discussed in chapter 5)
and then cast it specifically (using the as operator) to invoke the correct method.
Unlike the situation with example 6.1 step 3, these instances now bind to the required
methods. At compile time, the compiler knows that MyFurniture in line 37 must be treated
as a TChair because of the typecasting as operator. So in line 37, the MyFurniture object is
statically bound to TChair's GetKind method. Similarly in line 39, the compiler has enough
information at compile time to bind MyFurniture to TTable's GetKind method. Without any
typecasting in line 41, MyFurniture is statically bound to TFurniture's GetKind method
because MyFurniture is declared as TFurniture.
To test this, remove the typecasts in lines 37 and 39. Youll get the TFurnitures method
even with TChair and TTable instances. Alternatively, if you make the wrong typecasts in
lines 37 and 39 (and use the old Pascal-style typecasting which does no error checking), the
static binding will bind to the wrong methods, giving the wrong message:
Because we still have substitution, the order of evaluation in the conditional statements is
important. If we test first for the class highest in the hierarchy, in this case TFurniture, all
the subclasses will also evaluate true because any subclass can substitute for its superclass.
Static binding is slightly faster than dynamic binding, and so it is sometimes suggested
for speed critical operations. But all the IfElseIf comparisons introduce their own
performance penalty, counteracting the faster static binding, and introduce considerable
room for error.
In summary, using static binding introduces a lot of extra coding complexity, makes the
program more error prone, makes subsequent enhancement more brittle and generally does
not introduce a significant speed advantage. So it is well worth the effort of getting to
understand all the capabilities of polymorphism and to think out an applications
inheritance structures carefully to maximise generalisation and the possibilities for
substitution.
1
RTTI is not part of the .NET specification, but Delphi 8 for .NET still makes the is, as and similar
operators available.
Because of substitution, the programmer can assign MyFurniture to any of three types,
TFurniture, TChair or TTable. So for correct operation, Delphi must decide which of the
three methods to use while the program is running, not before. This requires late binding
(also called run-time binding or dynamic binding). (Binding refers to the link established
between the object and the method). Delphi uses late binding when the virtual and override
keywords are used in declaring the classes.
By default, Delphi uses early binding (also called compile-time or static binding). This
establishes the link between an object and a method while the program is being compiled,
before it runs. This means that Delphi cannot then invoke different methods depending on
the type of object involved at run-time and so early binding is non-polymorphic.
To support the future evolution of a program, the coupling within the program should be
kept low, particularly in those parts of a program that are subject to change. This is an
important reason for using polymorphism, and reducing coupling through polymorphism is
an important consideration in many patterns.
This example illustrates how the reduced coupling in a polymorphic program facilitates
future evolutionary growth. It also looks at combining inheritance for reuse and for
polymorphic behaviour. Well suppose we need to introduce another level of specialisation
into this program by subclassing TTable into TCoffeeTable and TKitchenTable (figure 6).
What changes must we make?
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 13
Ex 6.3 step 1 The additional subclasses
Figure 7 An additional
(polymorphic) level
Start with the polymorphic version of the program (example 6.1, step 4) and add the two
new subclasses (lines 1623, 4049 below, and remember about Class Completion
<Ctrl+Shift+C>). To show the combination of inheritance for reuse (subclassing) with
inheritance for polymorphism (subtyping), we have also changed the existing method
definitions to invoke the immediate ancestors method through the inherited keyword (lines
33, 38).
1 unit FurnitureU;
2 interface
3 type
4 {TFurniture and TChair definitions as before}
24 implementation
30 { TChair }
35 { TTable }
40 { TCoffeeTable }
45 { TKitchenTable }
Several different things are happening here, so lets look at them one by one. First we derive
two new subclasses, TCoffeeTable and TKitchenTable from TTable (lines 1623), so adding
another layer to the hierarchy (figure 7). These each have their own GetKind method
declared as override, which therefore override the GetKind method they would otherwise
inherit from TTable. These subclasses can use any of the methods they inherit from higher
up in the hierarchy, and can override any of the inherited methods dynamically (ie using
late binding to allow polymorphism) by using the override keyword provided that the
overridden method is either a virtual method or is itself an override method (eg line 14). So
GetKind in line 18 overrides its ancestors method which in turn overrides its ancestors
method (line 14).
We can override existing methods to whatever depth is needed. But what if we actually
want to be able to use an ancestors method even though it has been overridden? This turns
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 15
out to be quite easy: Delphi has the inherited keyword and this allows us to access the
ancestors method. This is useful when we dont want to replace the ancestral method, but
need to extend it for the descendant. In this case, we override the ancestors method, inherit
it to get its functionality and then add any code needed to extend it.
In summary, we replace an ancestral method by redeclaring the method in the subclass
with exactly the same name and parameter list as in the superclass. If we want this to be
polymorphic substitution using late binding, the root declaration of the method must be
virtual with an override declaration each time it is subsequently redeclared lower down the
hierarchy. Where we want to extend the ancestral method, we invoke it in the subclass
method through the inherited keyword.
This is the theory, at any rate. We havent seen it in action yet. So lets modify the driver
program to incorporate these additional classes.
Extend the user interface as shown in figure 6 by adding the items CoffeeTable and
KitchenTable to rgbFurniture. Extend the rgbFurnitureClick event handler as shown:
The only programming change we have to make in the client class is to instantiate and
assign these two new subclasses (lines 2930). Because of the late (runtime) binding, we make
no change in the statement that uses these instances (lines 3440 are unaltered from lines 3238
in example 6.1 step 2). This is the power of polymorphism. Each subclass knows how to do
whatever it needs to do as a result of its own and its inherited methods. If these methods
So, this is all very nice and slick, but havent we chosen our example rather carefully? Yes
we have, in order to concentrate on the essential principles. But there are different
circumstances that we should also be able to accommodate. For example, the overridden
methods were all similar in the previous example. What happens if they are very different?
Lets say our base class is called MyShape. As subclasses we have MyEllipse and
MyRectangle. We want to create a Draw method. MyShape.Draw does not make any sense
what does it draw, a rectangle or an ellipse? So although every MyShape subclass that we
create needs to have a Draw method, we cant define Draw for the base class in the usual
way.
In a situation like this we can use an abstract method. This is a method that we declare as
abstract in the superclass. Being abstract means that it does not have an implementation and
so does not exist in the superclass. Each subclass then provides its own implementation.
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 17
Maybe its easier to understand if we work through an example. Well create a TMyShape
class with an abstract Draw method and two subtypes, TMyEllipse and TMyRectangle. The
subtypes will have concrete Draw methods to draw either an ellipse or a rectangle (figure 8)
and so override the abstract declaration in the superclass. Overriding implies dynamic
binding, and so the abstract declaration must also be made virtual.
Start a new application. Add a second unit for the class definitions shown below.
1 unit MyShapesU;
2 interface
4 type
5 TMyShape = class (TObject)
6 public
7 procedure Draw (AnImage: TImage; ABorder: integer); virtual;
8 abstract;
9 end; // end TMyShape = class (TObject)
18 implementation
19 { TMyEllipse }
26 { TMyRectangle }
As indicated in figure 10, we have a base class, TMyShape (lines 59), derived from TObject
and two subclasses, TMyEllipse and TMyRectangle, derived from TMyShape (lines 1017).
Each of these declares a method Draw which is a procedure.
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 19
For TMyShape, method Draw is declared as virtual (lines 78), so that it can be overridden
dynamically by a subclass, and as abstract, which means that it does not have an
implementation. To show that it is abstract, TMyShapes Draw method is italicised in figure
10. Looking at its method signature given by the declaration, we see that the Draw method
has two parameters, a TImage where the object must be drawn, and an integer value
ABorder to specify how far from the edge of the TImage the object must be drawn. TImage
is part of Delphis VCL and displays a graphical image on a form. For more information,
consult Delphis online Help.
An implementation for Draw must be provided in every branch derived from TMyShape.
Thus, in the implementation section (lines 1833), there is an implementation for
TMyEllipse.Draw (lines 2025) and for TMyRectangle.Draw (lines 2732). Since
TMyShape.Draw is abstract it has no implementation. Methods like TMyEllipse.Draw or
TMyRectangle.Draw, which implement an abstract method, are often called concrete
methods.
Here, TMyShape establishes a type (only the Draw method in this example) which is
implemented in the subtypes TMyEllipse and TMyRectangle.
On the user interface (figures 8&9) place two ComboBoxes, a Button and an Image (from the
Additional tab).
1 unit PolyShapeU;
2 interface
3 uses
4 Windows, Messages, SysUtils, Variants, Classes, Graphics,
5 Controls, Forms, Dialogs, StdCtrls, ExtCtrls, MyShapesU;
6 type
7 TfrmPolyShape = class(TForm)
8 { standard RAD declarations }
15 private
16 MyEllipse: TMyEllipse;
17 MyRectangle: TMyRectangle;
18 end; // TfrmPolyShape = class(TForm)
19 var
20 frmPolyShape: TfrmPolyShape;
21 implementation
22 {$R *.dfm}
Here we change our previous approach slightly to emphasise slightly different aspects. We
declare two private data fields for frmPolyShape, one for each concrete type that we define
in MyShapeU (lines 1517) and instantiate them when we create the form (lines 2627).
Then, in btnDrawClick, we assign one or other of these to MyShape (lines 3940), a locally
declared TMyShape reference. Because of substitution, we can assign an instance of a child
class to a variable of the parent class. So, depending on whether line 38 or 39 is executed,
MyShape refers to either MyEllipse or MyRectangle. In line 41, we make a polymorphic call
using the MyShape reference and so draw either an ellipse or a rectangle based on the
assignment in the Case statement (lines 3841). This is because the GetKind methods are
declared as virtual and override methods and so use late binding. The statement
MyShape.Draw would call TMyShapes GetKind method only if MyShape is instantiated as
a TMyShape. Trying to do this would lead to an error because TMyShapes GetKind method
is abstract (example 6.4 step 1, lines 78) and does not have a concrete implementation.
The MyEllipse and MyRectangle objects persist for the life of the form since we create
them in the forms OnCreate event handler and free them in its OnDestroy event handler.
However, we never use these directly. Instead, we assign one or other to MyShape and use
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 21
that. This means that we do not have to create and free instances as we did in the previous
examples in this chapter. We never instantiate MyShape directly, but use it to hold a
reference to one of the objects we have created.
Strictly speaking, we dont need to free the objects in the forms OnDestroy event handler
since Delphi automatically releases all memory it holds when the program terminates.
However it does no harm to free these objects. Here it emphasises the create-use-free
sequence in using objects that we discussed in previous chapters and the concept that if an
object creates any other objects it generally has the responsibility of freeing them before it
itself is destroyed.
Going back to the definition of TMyShape and its descendants we see that both GetKind
methods have the same statement to clear the Image component and to place a border
around it (example 6.4 step 1 lines 22 & 29). Although this repetition is not crucial in a small
example like this, in principle we try to avoid duplication by placing repeated code into a
separate method, sometimes called a helper method because it is for use only within the class.
If we declare this method as part of the base class, as we do here in lines 910 below, each
subclass can access it through inheritance rather than defining it separately a nice bit of
reuse.
What kind of visibility should we assign to a helper method like this? We dont want to
make it public since other units would then also be able to use it, compromising
encapsulation. Here we define TMyShape, TMyEllipse and TMyRectangle in the same unit.
Delphis visibility specifiers apply to a unit2 , and so we can specify the parents helper
methods as private: the subclasses can still access them. If we code the subclasses in separate
units, we would have to make the helper method protected. This makes it accessible to the
parents class descendants, even if they are in a different unit, but not to anyone else.
Here we declare the helper method as protected in the superclass (lines 910) and invoke
it from the subclasses (lines 24, 31).
1 unit MyShapesU;
2 interface
3 uses ExtCtrls;
2
Delphi 8 introduces additional strictly private and strictly protected specifiers which refer to
the class structure only without concern for the units where they are declared.
20 implementation
21 { TMyEllipse }
28 { TMyRectangle }
35 { TMyShape }
Assigning references
In example 6.4 step 2 we declare an object reference (MyShape, line 32) but then dont ever
instantiate it. Instead, we assign it to one or other of the two objects we do create (line 39 or
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 23
40). Its worth looking at this briefly. First we look at the memory layout before the Case
statement runs (figure 11).
After the Case statement, and assuming that the user selected the Ellipse RadioButton,
MyShape refers to the same instance as MyEllipse (figure 12).
Notice two points. First, we can assign more than one reference to the same object. In figure
12, both MyEllipse and MyShape refer to the same object. We declared references to
TMyEllipse, TMyRectangle and TMyShape, but only created a TMyEllipse and a
TMyRectangle. In lines 39 or 40 we then assign an additional reference, MyShape, to one of
these objects. It is often important to be able to assign more than one reference to an object,
and this is one reason that Delphi (and Java) use reference semantics.
The second point to note is the principle of substitution: A variable that can hold a
reference to an object of class A can also hold a reference to an object of any subclass of A.
At times, alternative behaviour is needed on the basis of an objects type. One approach to
this is to use a series of If statements that test the objects type and then invoke the required
behaviour. However there are several problems associated with sets of If statements. They
Therefore,
when the required behaviour is determined by the type of an object, consider using a
polymorphic call instead of using an If statement. The programmer then does not need to
encode the logic for determining the type since the resulting behaviour is a product of both
the method call and the receiving object. Future changes, such as additional classes of the
same subtype, are accommodated by the polymorphic methods of the new classes rather
than by extending If statements. Thus the types that vary have the responsibility for the
correct behaviour. This reduces the possibility of introducing errors when If statements have
been changed incorrectly or have been overlooked. Polymorphism therefore can make code
simpler to implement and more reliable. Because polymorphism relies on substitution, and
so on the generalisation hierarchies in the program, designing for polymorphism can
improve the class inheritance structure and reduce coupling. (A single association link can
be used to navigate between all descendants of the two associated classes.)
The drawbacks of polymorphism are that it can make code more difficult to understand
and can introduce additional classes into the program structure.
Polymorphism is a crucial difference between object-oriented programming and other
programming styles. Used carefully, polymorphism produces systems that are simpler and
also more flexible, and so can be changed more easily to meet changing requirements. It
plays an important role in many patterns, and well be meeting it regularly in the remaining
chapters.
This chapter refers to the difference between subclassing and subtyping. Although this
distinction is crucial to using polymorphism effectively, not all authors maintain it. These
terms also sometimes mean different things to different people. We follow the Gang of Four
(GoF) definitions (Gamma et al, 1995, p17):
Class inheritance defines an object's implementation in terms of another object's
implementation. Thus class inheritance (or subclassing) allows for the re-use of data fields
and methods, and is a mechanism for code and representation sharing. It is therefore largely
a consideration that comes up during implementation and is the form of inheritance we
used in the first few chapters of these notes.
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 25
In contrast, interface inheritance (or subtyping) describes when an object can be used in
place of another. Subtyping is concerned about being able to substitute a child for a parent,
and consequently about polymorphism. It is largely a design consideration though its
implementation needs dynamic binding.
Subtyping allows a particular type or interface to be defined in a parent class. The
various child classes then implement this interface in such a way that any child may take on
the role nominally assigned to the parent. Client programs declare variables of the type of
the parent only so that they can remain unaware of the specific child class which is
dynamically bound at any particular moment to the parent type,
An important distinction here is the difference between static and dynamic binding. With
static binding, the message initiator determines how the message will be carried out
through addressing a specific class (the class declared) irrespective of the actual class of the
receiver. With dynamic binding, the message initiator addresses a specific type at run time
and the receiver performs its (own) implementation of the message.
What makes the difference between subclassing and subtyping a little difficult to grasp at
first is that both depend on inheritance. So its difficult to see from just a quick glance at a
set of class definitions whether subclassing, subtyping or both are being used. A useful clue
is given by the virtual, abstract and override keywords, which usually indicate that
subtyping is being used in some way or other. As we work further through these notes, the
distinction will become clearer.
Chapter 6 summary
Main points:
1. Substitution with programmer classes
2. Early (static, compile time) and late (dynamic, run time) binding
a. Virtual and override methods
3. Polymorphism = substitution + dynamic binding
a. Multiple associations through a single link, reduced coupling, evolvability
4. Abstract methods
5. The concept of a type
6. The Polymorphism Pattern
Objects as derived entities: Dynamic binding (virtual and override methods); abstract methods
Objects as interacting entities: Polymorphic messages and behaviour
Patterns: Polymorphism
Larman (2001) lists Polymorphism as one of his GRASP patterns (pp 326329). Grand (1999)
discusses the GRASP patterns, including Polymorphism (pp 6972). Gamma et al (1995)
emphasise that the distinction between class and type is very important in OO
programming and list several patterns that depend heavily on it (p17). They discuss
polymorphic iteration in particular as part of the Iterator pattern (pp 258259).
Gamma, E., Helm, R., Johnson, R. and Vlissides, J. 1995. Design Patterns: Elements of reusable
object-oriented software. Addison-Wesley, Reading, MA.
Grand, M. 1999. Patterns in Java, vol 2. Wiley: New York.
Larman, C. 2001. Applying UML and patterns: An introduction to object-oriented analysis and
design and the Unified Process, 2nd ed. Prentice Hall: New Jersey.
Problems
Problem 6.1 Study Chapter 6
Identify the appropriate example(s) or section(s) of the chapter to illustrate each comment
made in the summary at the end of chapter 6.
With static binding does the order of class testing make any difference? For instance, In
example 6.2 would it make any difference if the event handler were changed to the
following? Explain your answer.
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 27
23 lblKind.Caption := 'Object not defined'
24 else if (MyFurniture is TFurniture) then
25 lblKind.Caption := (MyFurniture as TFurniture).GetKind
26 else if (MyFurniture is TChair) then
27 lblKind.Caption := (MyFurniture as TChair).GetKind
28 else if (MyFurniture is TTable) then
29 lblKind.Caption := (MyFurniture as TTable).GetKind;
30 end; // procedure TfrmPolymorphism.btnTypeClick(Sender: TObject)
Convert example 6.3 from dynamic (late) binding to static (early) binding. Comment on the
differences between these two approaches.
Create a polymorphic program with three subclasses of Employees: fixed, weekly and
contract. All fixed rate Employees receive R2,500-00 per week. Hourly Employees receive
R50-00 per hour for the first forty hours worked in a week and R60-00 per hour for any extra
hours. Type 1 fixed contracts are worth R1,000-00 and type 2 are worth R2,500-00.
The programs main screen must provide some way for the user to select the Employee
type (as in figure 13).
For the fixed rate Employees the program merely displays the answer. However, for the
other two the user must provide further information (eg figure 14). The program code for
these input dialogues must appear in the methods that calculate the remuneration.
Imagine that you are mentoring several new programmers. They have worked through all
the examples given above but are still feel unsure about several of the concepts. Work out
additional examples for them to illustrate:
a) virtual / override methods, and
b) abstract methods.
Introducing type inheritance (26 Jun 2006, all rights reserved) Chapter 6, Page 29