Polymorphism

   


In general, polymorphism means the ability to have many forms. Before we look closer at its meaning in computer science, we need to make a couple of important observations that are related to the inheritance hierarchy.

An Object of a Descendant Class Has More Than One Type

Consider our chain of derived classes in Figure 16.9 of Chapter 16. In everyday life, most people agree that a racing car is a car, a car is a surface vehicle, and a surface vehicle is a transportation vehicle. However, the contrary is not true, a car is not necessarily a racing car. In computer science and in C#, this is also the case. An object of type SportsCar is therefore also of type Car and of type SurfaceVehicle and of type TransportationVehicle. Consequently, a variable of type Car can hold a reference to an object of type SportsCar, making the following two lines valid:

 Car myCar; myCar = new RacingCar(); 

However, the opposite is not true. A variable of type RacingCar cannot hold an object of type Car, because a Car is not necessarily a RacingCar. So, the following code is not valid:

 RacingCar myRacingCar; myRacingCar = new Car();   //Invalid 

We can extend this logic to the whole inheritance hierarchy, meaning that myCar of type Car can also hold a reference to either a FamilyCar object or a SportsCar object, and a variable of type TransportationVehicle can hold a reference to an object of a type of any of its descendants.

When you assign an object of a descendant type (such as RacingCar) to a variable of an ancestor type (such as Car) as follows:

 Car myCar = new RacingCar(); 

you can only call the method (and property and indexer) names defined for the ancestor type through the ancestor type variable (here called myCar). For example, if your Car class contains the MoveForward method, and its derived class RacingCar, apart from inheriting this method, also defines the StartOnBoardCamera method, you can still only make the following call through myCar:

 myCar.MoveForward(); 

but the following is invalid:

 myCar.StartOnBoardCamera();    //invalid 

because StartOnBoardCamera() is not defined in Car.

Dynamic Binding of Virtual Methods and (get, set) Accessors

Let's continue our small Car and RacingCar example from the previous section where we performed the following assignment:

 Car myCar; myCar = new RacingCar(); 

Suppose we make a call to MoveForward from myCar as follows:

 myCar.MoveForward(); 

Both the Car class and the RacingCar class contain an implementation for the MoveForward method, so the runtime must now make an important choice: It can either invoke the MoveForward implementation of the Car class or the MoveForward implementation of the RacingCar class. If MoveForward is declared virtual in the Car class and overridden in the derived RacingCar class with the override keyword, the runtime will call the method defined for the RacingCar.

When the program with the declaration

 Car myCar; 

is compiled, the compiler cannot possibly know whether myCar will contain a Car object or a RacingCar object or perhaps a SportsCar or FamilyCar object when it meets the following line

 myCar.MoveForward(); 

How, then, is it possible to execute the MoveForward implementation for the RacingCar class if myCar contains a RacingCar object; or execute the MoveForward implementation for the SportsCar class if it contains a SportsCar object; or the implementation for the Car class if it contains a Car object? C# uses a mechanism called dynamic binding, late binding (or virtual dispatch) that has taken these names for the following reason: Even though we know the name of the method being called at compile time, with the following line

 myCar.MoveForward(); 

the actual implementation this name represents is determined dynamically (hence the name dynamic binding) and later than compile time (hence the name late binding) while the program is being executed. Figure 17.1 provides an overview of the dynamic binding mechanism. It says that if, for example, myCar has been assigned a SportsCar object with the following line:

Figure 17.1. Dynamic binding in action.
graphics/17fig01.gif
 myCar = new SportsCar(); 

then the line

 myCar.MoveForward(); 

will execute the SportsCar class's version of MoveForward.

Directly translated, polymorphism means the ability to have many forms. In computer science, it means using a single variable to reference objects of different classes and, through the dynamic binding mechanism, letting the method that is implemented for the class of the referenced object be called automatically. So, dynamic binding and polymorphism are more or less different words for the same thing. However, whereas dynamic binding refers to the mechanical process that takes place in the computer, polymorphism is a concept used for more abstract discussions about objects and classes.

Note

graphics/common.gif

A computer language must at least support the notion of classes and objects along with encapsulation, inheritance, and polymorphism to deserve the label of being an object-oriented language.


Note

graphics/common.gif

Sometimes, method overloading is also referred to as polymorphism because different method signatures with the same name have different implementations. However, as object-oriented programming has matured, polymorphism is used most commonly in connection with the dynamic binding mechanism.


Case Study: Using Polymorphism to Create a Simple Drawing Program

An architectural drawing can be viewed as a collection of shapes, where a shape can be a circle, a square, a triangle, and so on. Suppose we were asked to write an architectural drawing program so that architects could create their drawings on the computer screen. One of the fundamental requirements of this program would be to represent a drawing and to draw this representation onscreen whenever requested. Most of this case study is concerned with finding an elegant way to solve this problem and designing a simplified version of a program with this fundamental ability.

First, we set out in the usual manner to determine the classes we need to include in our program by rephrasing the first sentence of this case study and highlighting the nouns: A drawing consists of a collection of shapes. A shape can be (or is) a circle, a rectangle, a triangle, and so on. Accordingly, we need to implement a Shape class and, just like the Car class had three different subclasses, a Shape class also has several subclasses. In this case, we will limit the program to contain the three Shape subclasses Circle, Rectangle, and Triangle, as shown in Figure 17.2.

Figure 17.2. Circle, Rectangle, and Triangle are subclasses of Shape.
graphics/17fig02.gif

Apart from the Shape class and its three subclasses, we need to implement a collection of shapes. To this end, we will use the well-known array.

Note

graphics/common.gif

After an array has been defined, it has a fixed length. This is not an optimal behavior in this case because a drawing, in most cases, consists of an unknown number of shapes at the time it is begun. An ArrayList, which can grow and shrink dynamically, would be more appropriate. However, whether we use an array or an ArrayList does not interfere with our ability to demonstrate the power of polymorphism.


As the architect adds shapes to the drawing, different kinds of corresponding Shape objects (in our case, Circles, Rectangles, and Triangles) are created in the program. Each Shape object's details, such as position on the drawing, its size, color, and so on, are stored inside the individual object. It is then added to our collection of objects, as shown in Figure 17.3.

Figure 17.3. An array of shapes represents a drawing.
graphics/17fig03.gif

How can different shape types be inserted into the same array when all elements of the array must have the same type? Because Circle, Square, and Triangle are all descendants of Shape, any objects of one of those three types can be referenced by a variable of type Shape. Consequently, if we declare our array to be of type Shape, each element can store a Circle, Square, or a Triangle.

After the array of shapes has been created, we need the ability to draw its collection of Shape objects onscreen. To draw an object, we could attempt to write a method in a separate class called DrawingMachine that might contain a method called MakeDrawing. This method would look at each object in the array and at the instance variables of each object to determine its position, size, and shape. However, this approach would be cumbersome because each object requires different parameters and different algorithms to be drawn. For example, a circle needs a radius and a center point, whereas a rectangle needs a height and a width, and other more intricate shapes have many more parameters. Instead of using this tedious and cumbersome technique, we turn to polymorphism, which provides an elegant way to solve this problem.

Polymorphism allows us to support the following scenario: Instead of trying to figure out how each object is drawn from outside of the object, it makes more sense to let the object carry its own implementation for how it is drawn. Each object then forms a neat encapsulated package with the data as well as the implementation needed to draw itself. Let's call the method that draws the object DrawYourself. Any shape object contains a DrawYourself method, so the Shape class would also have a DrawYourself method. However, just as we didn't know the implementation for the MoveForward method in the Car class, we also don't know the implementation of the DrawYourself method in the Shape class. We must declare DrawYourself to be abstract in the Shape class and then write its implementations in the three subclasses.

Recall that an abstract method is also virtual, so if we override the DrawYourself method in each of the classes derived from Shape, the dynamic binding mechanism will be applied on them. This means that we can now simply draw the drawing by iterating through the collection of shapes and asking each shape to DrawYourself. The dynamic binding mechanism will determine which kind of shape object is stored in each array element and automatically call the appropriate DrawYourself method.

Listing 17.2 contains a simple implementation of the Shape class (lines 3 6) and its three subclasses. Notice that DrawYourself is declared abstract in the Shape class and is overridden in each of the three subclasses. The Shape classes have been kept simple and do not contain any information about position or size. They always produce just one shape, which is drawn on the console.

Please note that the source code in Listing 17.2 lacks a Main method and does not compile. In a moment, we will look at the code that puts the four classes to good use.

Listing 17.2 ThreeShapes.cs
01: using System; 02: 03: public abstract class Shape 04: { 05:     public abstract void DrawYourself(); 06: } 07: 08: public class Triangle : Shape 09: { 10:     public override void DrawYourself() 11:     { 12:         Console.WriteLine("         *     "); 13:         Console.WriteLine("        * *    "); 14:         Console.WriteLine("       *   *   "); 15:         Console.WriteLine("      *     *  "); 16:         Console.WriteLine("     *_______* "); 17:     } 18: } 19: 20: public class Circle : Shape 21: { 22:     public override void DrawYourself() 23:     { 24:         Console.WriteLine("        ***     "); 25:         Console.WriteLine("     *       *  "); 26:         Console.WriteLine("    *         * "); 27:         Console.WriteLine("    *         * "); 28:         Console.WriteLine("     *       *  "); 29:         Console.WriteLine("        ***     "); 30:     } 31: } 32: 33: public class Rectangle : Shape 34: { 35:     public override void DrawYourself() 36:     { 37:         Console.WriteLine(" ------------------ "); 38:         Console.WriteLine("|__________________|"); 39:     } 40: } 

To ascertain ourselves that dynamic binding is in fact taking place when we call the DrawYourself method through a variable of type Shape, I have written the code snippet in Listing 17.3. You need to attach this listing at the end of Listing 17.2 to make it work. Your output should then resemble the output shown in the output sample after Listing 17.3.

Listing 17.3 ShapesTester.cs
01: class ShapeTester 02: { 03:     public static void Main() 04:     { 05:         Shape myShape; 06: 07:         myShape = new Circle(); 08:         myShape.DrawYourself(); 09:         myShape = new Triangle(); 10:         myShape.DrawYourself(); 11:         myShape = new Rectangle(); 12:         myShape.DrawYourself(); 13:     } 14: }         ***      *       *     *         *     *         *      *       *         ***          *         * *        *   *       *     *      *_______*  ------------------ |__________________| 

myShape is declared to be of type Shape in line 5 of Listing 17.3. Consequently, myShape can reference any object of type Circle, Triangle, and Rectangle because they are descendants of the Shape class.

Even though lines 8, 10, and 12 in Listing 17.3 contain the same call to DrawYourself from the myShape variable, each call results in the execution of a different implementation of DrawYourself because of the dynamic binding mechanism. Line 8 results in the execution of the Circle version of DrawYourself because myShape at that moment contains a Circle object. The sample output confirms this by first drawing a circle. Line 10 results in the Triangle implementation of the DrawYourself method being executed because myShape has just been assigned a Triangle object in line 9. Line 12 follows the same logic.

The simple test in Listing 17.3 gives us a glimpse of the tremendous expressive capability polymorphism provides us with. Each time we call myShape.DrawYourself(), we do not have to concern ourselves with which object is being stored in myShape or any other details related to this object. All we need to care about is that a shape is being drawn. In other words, polymorphism allows us to draw a drawing on a high level of abstraction.

Let's use this tremendous power to complete our drawing program, as shown in Listing 17.4.

Note

graphics/common.gif

Please remove Listing 17.3 from the program you ran before and insert the code in Listing 17.4. To test this new program, you can use the code provided in Listing 17.5, which can be inserted after the code in Listing 17.4.


Listing 17.4 DrawingEngine.cs
01: public class DrawingEngine 02: { 03:     public Shape [] CreateDrawing() 04:     { 05:         int numberOfShapes = 0; 06:         int shapeCounter = 0; 07:         string choice; 08:         Shape [] drawing; 09: 10:         Console.Write("How many shapes do you want in your drawing? "); 11:         numberOfShapes = Convert.ToInt32(Console.ReadLine()); 12:         drawing = new Shape[numberOfShapes]; 13:         do 14:         { 15:             Console.Write("Choose next shape: C)ircle R)ectangle T)riangle: "); 16:             choice = Console.ReadLine().ToUpper(); 17:             switch(choice) 18:             { 19:                 case "C": 20:                     drawing[shapeCounter] = new Circle(); 21:                     break; 22:                 case "R": 23:                     drawing[shapeCounter] = new Rectangle(); 24:                     break; 25:                 case "T": 26:                     drawing[shapeCounter] = new Triangle(); 27:                     break; 28:                 default: 29:                     Console.WriteLine("Invalid choice"); 30:                     shapeCounter ; 31:                     break; 32:             } 33:             shapeCounter++; 34:         }  while (shapeCounter < numberOfShapes); 35:         return drawing; 36:     } 37: 38:     public void DrawDrawing(Shape [] drawing) 39:     { 40:         for(int i = 0; i < drawing.Length; i++) 41:         { 42:             drawing [i].DrawYourself(); 43:         } 44:     } 45: } 

The purpose of the CreateDrawing method (lines 3 36) of Listing 17.4 is to let the user create any sequence of Shape objects and return this collection of objects in an array of Shape elements.

Line 8 declares an array called drawing with elements of type Shape. This allows the user to insert either a Circle (line 20), a Rectangle (line 23), or a Triangle (line 26) into the next element of this array. After each of the drawing elements holds a reference to a shape object, drawing is returned to the caller in line 35.

The DrawDrawing method (lines 38 44) takes as an argument an array of Shapes and prints the contents of this array onscreen. DrawDrawing shows the advantages of using polymorphism. To draw every shape contained in the drawing array passed to it, DrawDrawing merely needs to iterate through the array and call the DrawYourself method for each of its elements. We don't need to worry about which object is stored in which element and how it is drawn; we just ask it to draw itself.

Notice how easy it is to extend our drawing program with other Shape classes. For example, to add an Octagon class, we merely need to derive it from the Shape class and provide an implementation for the DrawYourself method. We then need to allow the user to add Octagon objects to the drawing, but the DrawDrawing method remains unchanged; it doesn't care whether the shape is an octagon, a hexagon, or something else, as long as it is derived from the Shape class, because it is then guaranteed to have a DrawYourself method.

Listing 17.5 demonstrates Listing 17.2 and Listing 17.4 and must, as stated before, be combined with those two listings to work.

Listing 17.5 TestDrawingEngine.cs
01: class TestDrawingEngine 02: { 03:     public static void Main() 04:     { 05:         Shape [] myDrawing; 06:         DrawingEngine myCAD = new DrawingEngine(); 07:         string choice; 08: 09:         do 10:         { 11:             Console.WriteLine("Please prepare to create drawing"); 12:             myDrawing = myCAD.CreateDrawing(); 13:             do 14:             { 15:                 Console.WriteLine("Here is your beautiful drawing\ n"); 16:                 myCAD.DrawDrawing(myDrawing); 17:                 Console.Write("\ nDo you want to see it again? Y)es N)o "); 18:                 choice = Console.ReadLine().ToUpper(); 19:             }  while(choice != "N"); 20:             Console.Write("Do you want to create another drawing?Y)esN)o "); 21:             choice = Console.ReadLine().ToUpper(); 22:         }  while(choice != "N"); 23:     } 24: } Please prepare to create drawing How many shapes do you want in your drawing? 3 Choose next shape: C)ircle R)ectangle T)riangle: t Choose next shape: C)ircle R)ectangle T)riangle: r Choose next shape: C)ircle R)ectangle T)riangle: c Here is your beautiful drawing          *         * *        *   *       *     *      *_______*  ------------------ |__________________|         ***      *       *     *         *     *         *      *       *         *** Do you want to see it again? Y)es N)o n Do you want to create another drawing? Y)es N)o n 

Listing 17.5 uses the CreateDrawing method in line 12 to create a new array of shapes and assigns it to the myDrawing array. It then provides this array as an argument to DrawDrawing in line 16 to draw the shapes contained in myDrawing onscreen.

Polymorphism allows us to concentrate on generalities and lets us leave the specifics to the runtime engine. Even without knowing the exact types of a group of objects, we can still give commands to those objects and have them executed in manners appropriate to those objects. Polymorphism also facilitates extensibility, because polymorphic method calls are type independent to a certain degree.

Inheritance and polymorphism have revolutionized the way software is written. What follows are a couple of examples where polymorphism could be used.

Computer games include different objects that move, act, and are displayed onscreen. These can be spaceships, aliens, cars, monsters, and so on. We can create a common base class for the game participants called, say, GameElement, which can, among other methods, contain an abstract method called DrawYourself. Even though we have a collection of very different GameElements, each with a specific way of being drawn onscreen, we simply need to iterate through this collection and call the DrawYourself method when we want the pieces to be drawn onscreen.

An employee database system contains many different kinds of employees, such as programmers, secretaries, marketing researchers, cleaners, directors, and so on. Even though all these employees have differences, they all share many attributes, such as name, address, and birth date. They also have common methods, such as a method for calculating the salary (here called CalculateSalary). We can create a base class called Employee from which all the different employee classes are derived. By equipping Employee with common employee attributes and methods, such as CalculateSalary, it becomes possible to iterate through the collection of employees and simply call the CalculateSalary method when salary payments are due. We don't need to know whether the actual employee called is a secretary object or cleaner object because behind the scenes, through dynamic binding, an appropriate method is being called a method that matches the particular object type referenced by the Employee variable that CalculateSalary was called through.


   


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net