Chapter 9. Polymorphism

Table of Contents

Polymorphism comes from greek:

  • poly = many

  • morph = form

Example: Odo from "Deep Space Nine"

In OO there are two definitions:

Important

Polymorphism
  1. Polymorphism is the facility by which a single operation or attribute name may be defined upon more than one class and may take on different implementations in each of those cases.

  2. Polymorphism is a property whereby an attribute or variable may point to (=hold the handle of) objects of different classes at different times.

So what does that mean?

Example: Imagine a class for polygons:

Figure 9.1. A class for polygons
A class for polygons

Sidenote: Remember derived attributs? area is a derived attribute.

If we where to look into the implementation of getArea() is would have to be very very very very very complicated to support any polygons.

Now how about some special cases of polygons?

Figure 9.2. Polygon with some subclasses
Polygon with some subclasses

getArea() for Triangle and for Rectangle are very easy (for Hexagon it is still complicated). Can't we just redefine them for the subclasses with some more efficient algorithms? Yes, we can!

Figure 9.3. Polygon with subclasses that overwrite methods
Polygon with subclasses that overwrite methods

Now we can implement an easier version of getArea() for Triangle and for Rectangle. We say "Rectangle overrides the method getArea()"

Important

Overriding

Overriding is the redefinition of a method defined on a class C in one of C's subclasses.

Rules for overriding:

  • Methods can be overridden

  • Attributes can never be overridden

  • In C++ (and most other languages, e.g. Java) overridden methods must have the exact same signature (=name, capitalization, parameter list)

  • In C++ (but not in most other languages) overridden methods may have a different return type (although you should use that with caution)

  • In C++ overridable methods have to be marked with the "virtual" keyword (but more of that later)

Practice: Show an inheritance diagram of the classes "Aircraft", "Glider" and "Boing747". Assume that every aircraft has an operation "startEngines()". Boing747 will just use that operation. Glider will have to override it, since it has no engine.

Let's assume the following code:

twoDShape.getArea; // Book's notation
twoDShape->getArea(); // C++ notation

We may not know which algorithm gets executed, this depends on the Class that the object two2DShape belongs to. We have to look at several cases:

  1. twoDShape is an instance of Triangle, the operation getArea() from Triangle will be executed.

  2. twoDShape is an instance of Recangle, the operation getArea() from Rectangle will be executed.

  3. twoDShape is an instance of Hexagon. Hexagon has no getArea() function, so the one from Polygon is used.

  4. twoDShape is an instance of Polygon, the operation getArea() from Polygon is used.

  5. twoDShape is an instance of another class C that is not related to Polygon. Since that class does not have an operation getArea() we will get an error (compile-time in C++)

But how can I not know which class an object belongs to???

The answer is: Since every object is an instance of all of its superclasses, we can use it every place where we can use the superclass (Assignments, as parameters)! Here are some examples:

var p: Polygon;
var t: Triangle := Triangle.New;
var h: Hexagon := Hexagon.New;
...
if user sayas OK
then p := t
else p := h
endif;
...
p.getArea; // P may be a Triangle or a Hexagon object
...

Or in C++ notation:

Polygon *p;
Triangle *t = new Triangle();
Hexagon *h = new Hexagon();
...
if (userSaidOk)
  p = t;
else
  p = h;
...
p->getArea(); // P may point to a Triangle or a Hexagon
...

Even though we assign an object of class Triangle (or Hexagon) to an object handle for Polygons, the object will still keep its class, so it will still execute the getArea() function from Triangle (or Polygon in the case of Hexagon)

In classical programming we would need to test of what type p really is and have some complicated switch-statement.

Advantage: We can now add new classes that are subclasses of polygon and overwrite getArea() without changing any of the existing code!

The general declaration "var p:Polygon" (Polygon *p) is a safety restriction. It makes sure that:

  • p can only hold handles to objects of class Polygon or its subclasses. (e.g. we cannot assign it an object of class "Consumer")

  • we can only execute the operations defined on Polygon, or we will get a compiler error.

The operation getArea() is an example of the first definition of polymorhpism.

The variable p is an example of the second definition of polymorphism.

Practice:

Assume the following classes:

Figure 9.4. Inhertiance diagram for Aircrafts
Inhertiance diagram for Aircrafts

And the following code:

Aircraft *a = new Aircraft();
Boing747 *b = new Boing747();
Glider *g = new Glider();
...
a->startEngines(); 1
b->startEngines(); 2
g->startEngines(); 3
...
a = g;
a->startEngines(); 4
...
a = b;
a->startEngines(); 5

which startEngines() functions are called in this code? Given an answer for the numbered lines.

Sidenote: Here we are losing an object, which one?

How does polymorphism work? Through dynamic binding:

Important

dynamic binding

Dynamic binding (or run-time binding or late binding) is the technique by which the exact piece of code to be executed is determined only at run-time (as opposed to compile-time)

The environment inspects the class at the last possible moment: at run-time, when the message is sent.

Do not confuse overriding with overrloading. Remember overloading from the intro class? We had function overloading and operator overloading.

overloading

overloading of a name or symbol occurs when several operations (or operators) defined on the same class have that name or symbol but different parameters. We say the name or symbol is overloaded.

Overriding: Superclass declares operation, subclass overrides it. Example: Aircraft declares startEngines(), Glider overrides startEngines().

Overloading: In the same class, but with different arguments! Example: Aircraft defines startEngines() and startEngines(toWhichPower:float).

Overloading is done by signature and can actually be checked at compile-time.

Some people (including me) sometimes call overloading polymorphism but that is incorrect! Only overriding is polymorphism.

Book: Chapter 1.8

Abstract operations

No we can override methods and declare superclasses with operations.

But what if an operation does not make sense on a superclass? What if the superclass is actually "useless" and just used for polymorhpism?

Figure 9.5. Example operation on vehicles
Example operation on vehicles

In this example, we would never instantiate an object of class "StreetVehicle". We would use the class StreetVehicle only to combine Motorcyle and Car. We would instantiate objects of class Car or Motorcycle and use them as StreetVehicle. The importance of every StreetVehicle is that is has an operation getCurrentSpeed() !

Then why even bother implementing getCurrentSpeed() on StreetVehicle? Instead, we can make it an abstract operation.

Important

abstract operation

An operation is lacking a a workable implementation. Normally, a descendent class will override this inherited abstract operation with its own concrete operation

Unfortunately, when a class has at least one abstact operation we will also need to make the class abstract

Important

abstract class

A class from which objects cannot be instantiated (normally because the class has one or more abstract operations). An abstract class is usually used as a source for descendant classes to inherit its concrete (nonabstract) operations.

The reason: Would we instantiate StreetVehicle and call its operation getCurrentSpeed(), but Streetvehicle does not implement it, we don't know what to do.

Abstract classes and operations are shown in UML in italics.

Figure 9.6. Vehicles with abstract superclass
Vehicles with abstract superclass

Unfortunately you can not write italics on paper (you may be able to but I am not able to distinguish your italics from non-italics. Therefore if you want to denote abstract classes or operations in this class on paper, you have to use the stereotype <<abstract>>. I do not want you to write things like "this is italics" or something, since that would not be correct UML!

Figure 9.7. Abstract object as done on paper
Abstract object as done on paper

The book uses an alternate notation. The use UML constraints (similar to stereotypes, but written in { } ) to denote abstract classes on paper. They would write {abstract}.

While stereotypes go in front of the method / class names, constraints go after the the declaration. Example: StreetVehicle {abstract}. See the book for an example.

You may use the books notation if you like or the stereotypes notation! But deceide on either one and do not mix them in the same diagram!

Book: Chapter 3.7

Practice: From the Vehicle example, draw an class diagram showing the classes Aircraft, Helicopter, and Plane. Add an operation getCurrentHeight() that is only defined for Helicopter and Plane, but specified on all Aircraft.

Pure abstract classes (interfaces)

A class that has has no attributes and only abstract operations is called a pure abstract class or interface.

Notes

  • A pure abstract class may be a subclass of one or more pure abstract classes. (but not of abstract classes or regular classes).

  • Since pure abstract classes define no attributes and only abstract operations they cause no problems with multiple inheritance

To denote a pure abstract class, you may use the stereotype <<interface>> on the class name. If you do your design on paper, thats enough. If you use a modelling tool you still have to italicize your class name and operations!

Practice: Draw a class diagram for the classes Chair, Table, Floor, and the pure abstract class Seat. Seat defines an operation sitOn() that the other three classes override.