Polymorphism


Polymorphism is one of the major goals of inheritance. With polymorphism, related types can be treated in a generic manner. In a graphics program, instead of calling separate algorithms or operations for rectangle, ellipse, and triangle shapes, you leverage their commonality and call a generic algorithm or operation. This is more extensible and maintainable than handling each type of geometric shape differently.

The following program draws rectangles, ellipses, and triangles:

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             Rectangle shape1=new Rectangle();             Rectangle shape2=new Rectangle();             Ellipse shape3=new Ellipse();             shape1.Draw();             shape2.Draw();             shape3.Draw();         }     }     public abstract class Geoshape {         public Geoshape() {             ++count;             ID=count;         }         private static int count=0;         protected int ID=0;         public virtual void Draw() {         }     }     public class Rectangle: Geoshape {         public override void Draw() {             Console.WriteLine("Drawing Shape {0} : rectangle",                 ID.ToString());         }     }     public class Triangle: Geoshape {         public override void Draw() {             Console.WriteLine("Drawing Shape {0} : triangle",                 ID.ToString());         }     }     public class Ellipse: Geoshape {         public override void Draw() {             Console.WriteLine("Drawing Shape {0} : ellipse",                 ID.ToString());         }     } } 

The two primary advantages to polymorphism are late binding and extensibility:

  • Late binding is when a specific function call is decided at run time. Early binding sets the function call at compile time, which is not always feasible. For example, in a competent graphics program, users decide the objects to draw at run time. The previous program does not accommodate this. The same objects are drawn every time.

  • Extensible code adapts easily to future changes. In the preceding code, the Draw method is called separately for each kind of geometric type. A rectangle is drawn differently from an ellipse. As the program evolves into the future, more geometric shapes are likely to be added. It could eventually support 50 types of shapes. An extensible process for drawing a variety of shapes is needed. Do you want to call Draw 50 different times—once for each type of shape?

Polymorphism starts with a base class reference to an instance of a derived type. Assign an instance of a derived type to a base class reference. When a virtual function is called on the base class reference, the compiler automatically delegates to the overridden method in the derived instance. Virtual and overridden methods have a special relationship. Virtual methods delegate to overridden methods when possible. BaseReference.VirtualMethod delegates to different implementations depending on the derived object assigned to the base class reference.

In the following code, Geoshape is the base class reference, and Draw is the virtual method. At run time, two derived instances are created and assigned to the base class reference. Geoshape references shape[0] and shape[1] are assigned an Ellipse and Rectangle object, respectively. The Draw method is invoked on the Geoshape base class reference. At that time, the compiler delegates to the Draw methods overridden in the derived types (base.virtualfunc-> derived. overriddenfunc). Therefore, a virtual function has different behavior depending on the kind of object assigned to the reference.

 Geoshape [] shapes={ new Ellipse(),                      new Rectangle() }; shapes[0].Draw();   // Geoshape.Draw()->Ellipse.Draw() shapes[1].Draw();   // Geoshape.Draw()->Rectangle.Draw() 

In the following code, the DrawShape method draws a geometric shape. Can you predict which type.Draw is called? The method has one parameter, which is the Geoshape base class. This is a reference to a base class. When a Rectangle instance is passed as a parameter, it is a derived instance to a base reference. This is one of the requirements of polymorphism. The base reference points to Rectangle instance, which means Rectangle.Draw is called. When the Triangle instance is passed as a parameter, Triangle.Draw is called. Therefore, one statement, shape.Draw, calls different implementations for related types. The shape.Draw statement is extensible.

 public static void Main(){     DrawShape(new Ellipse());     DrawShape(new Rectangle()); } public static void DrawShape(Geoshape shape) {     shape.Draw();  // which Draw is called? } 

Polymorphism requires three things:

  • Related classes

  • Common method

  • Different behavior

The following code demonstrates polymorphism. There are related types: Triangle, Ellipse, and Rectangle. The common method is Draw. Each Draw method is implemented differently in the derived class. At the command line, clients indicate which shapes to draw. This command draws a rectangle, ellipse, triangle, and another rectangle. Therefore, the decision about what to draw is deferred to run time:

 shapes r e t r 

The program defines a collection of base references. At compile time, we know that clients will draw shapes but we don't know the specific shape. Therefore, a collection of Geoshape base class references is defined, which is a generalization of all shape types. Plus, polymorphism begins with base references to derived instances. In the first loop, derived instances are created and added to the collection of base class references. In the second loop, the array of Geoshape base class references is iterated. Rectangle.Draw, Ellipse.Draw, or Triangle.Draw is called based on the type of derived object assigned to the base reference. Therefore, each call to the Geoshape.Draw could initiate a different operation. This is the polymorphic behavior.

 using System; using System.Collections.Generic; namespace Donis.CSharpBook{     public class Starter{         public static void Main(string [] shapeArray){            List<Geoshape> shapes=new List<Geoshape>();            Geoshape obj=null;            foreach(string shape in shapeArray) {                if(shape.ToUpper()=="R") {                    obj=new Rectangle();                }                else if(shape.ToUpper()=="E") {                    obj=new Ellipse();                }                else if(shape.ToUpper()=="T") {                    obj=new Triangle();                }                else {                    continue;                }                shapes.Add(obj);            }            foreach(Geoshape shape in shapes) {                shape.Draw(); // polymorphic behavior            }        }     }     public abstract class Geoshape {         public Geoshape() {             ++count;             ID=count;         }         private static int count=0;         protected int ID=0;         public abstract void Draw() {         }     }     public class Rectangle: Geoshape {         public override void Draw() {             Console.WriteLine("Drawing Shape {0} : rectangle",                 ID.ToString());         }     }     public class Triangle: Geoshape {         public override void Draw() {             Console.WriteLine("Drawing Shape {0} : triangle",                 ID.ToString());         }     }     public class Ellipse: Geoshape {         public override void Draw() {             Console.WriteLine("Drawing Shape {0} : ellipse",                 ID.ToString());         }     } } 

Geoshape is an abstraction and not concrete. You cannot draw a generic geometric shape. For that reason, in the Geoshape class, the Draw method should also be abstract. There is an added benefit of an abstract Draw in the base class. Derived types are forced to implement a specific Draw method. They are not inheriting the Draw method of the base class, which does nothing:

 public abstract void Draw(); 

Interface Polymorphism

Interface polymorphism employs base interfaces instead of base classes. The result is the same. Because it has some implementation, the Geoshape class cannot be entirely replaced with an interface. However, the Draw abstract method can be lifted from the class and placed in an IDraw interface:

 using System; using System.Collections.Generic; namespace Donis.CSharpBook{     public class Starter{         public static void Main(string [] shapeArray){             List<Geoshape> shapes=new List<Geoshape>();             Geoshape obj=null;             // partial listing             foreach(IDraw shape in shapes) {                 shape.Draw();             }         }     }     public interface IDraw {         void Draw();     }     public abstract class Geoshape: IDraw {         public Geoshape() {             ++count;             ID=count;         }         private static int count=0;         protected int ID=0;         public abstract void Draw();     } // partial listing 

New Modifier and Polymorphism

The new modifier interrupts normal polymorphism and can cause unexpected results. Unlike an overridden member, the compiler will not delegate from a virtual member to a member that has the new modifier. They are considered unrelated.

The following code demonstrates the potential problem. ZClass.MethodA method is virtual and polymorphic. YClass.MethodA overrides ZClass.MethodA. XClass derives from YClass. In the XClass, the new modifier defines an unrelated MethodA. XClass.MethodA is also marked avirtual. WClass.MethodA overrides XClass.MethodA. ZClass.MethodA and YClass.MethodA define a group of related methods. XClass.MethodA and WClass.MethodA define a second group. In Main, several related objects are instantiated. The derived instances are cached in an array of ZClass references, which is an array of base class references. In the loop, MethodA is called through the derived objects.

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             ZClass [] zArray={                  new ZClass(),                  new YClass(),                  new XClass(),                  new WClass(),                  new YClass(),                  new ZClass() };             foreach(ZClass obj in zArray) {                 obj.MethodA();  // polymorphic behavior             }         }     }     public class ZClass {         public virtual void MethodA(){             Console.WriteLine("ZClass.MethodA");         }     }     public class YClass: ZClass {         public override void MethodA(){             Console.WriteLine("YClass.MethodA");         }     }     public class XClass: YClass {         public new virtual void MethodA(){             Console.WriteLine("XClass.MethodA");         }     }     public class WClass: XClass {         public override void MethodA(){             Console.WriteLine("WClass.MethodA");         }     } } 

The following code shows the result of running the application. Notice that XClass.MethodA and WClass.MethodA are not called. The new modifier on the XClass.MethodA method interrupts the polymorphic hierarchy.

 C:\ >newapp ZClass.MethodA YClass.MethodA YClass.MethodA YClass.MethodA YClass.MethodA ZClass.MethodA 




Programming Microsoft Visual C# 2005(c) The Language
Microsoft Visual Basic 2005 BASICS
ISBN: 0619267208
EAN: 2147483647
Year: 2007
Pages: 161

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