KnowDotNet Visual Organizer

Polymorphism Done Right

and the virtues of Virtual Methods

by William Ryan
Print this Article Discuss in Forums

Recently I was working on some articles for our new Refactoring Section.  In the process I wanted to be able to explain why a 'bad' habit is in fact bad.  After all, what value is refactoring something if it's not an improvement.  In some instances things are black and white, some things just plain old stink. In others it's a matter of ok versus better or better versus best.  I was working on the Extract Interface refactoring and I got to thinking about Polymorphism.  I remember a while back when a former company of mine was interviewing VB programmers and one of the interview questions was "How do you implement Polymorphism in VB" (this was VB6.  The guy made three totally unrelated classes, and provided a method for each one called DoSomething.  He said that this was really limited but that VB wasn't his strong point, as far as he knew it didn't support inheritance and that if we wanted to see how to correctly implement it, let him write it in C++ or Java.  That he did and eventually he got the job.

Well both VB.NET and C# Support inheritance and by extension, REAL Polymorphism.  If you look at the defintion I provided, an integral part of the definition is:  

"
More specifically, it is the ability to redefine methods for derived classes. "
The emphasis added was by the author, not me.  Anyway, the key phrase here is Derived Classes! So, if you don't have derived classes you don't have real polymorphism!  Anyway, let's look at an example of the wrong way to implement polymorphism in C#:
First, I'm going to define a class called
Computer, from that I'll derive two classes, DataBaseServer and WebServer.  They'll each have a method called ShowDetails . (Before I go on, I'll admit I got a little lazy.  I have multiple overloaded constructors in the base class and I only implemented one constructor in the derived class.  In practice I'd have a matching constructor in each of the derived classes for each constructor in the base class).  I also created a form with Two Buttons, BadTest and GoodTest.  I create an Array of Computer Objects, and add an instance of each derived class to the array.  From there, I call a function named ShowDetails (I wasn't really original tonight in my naming conventions, but this showDetails has no relation to the class' showDetails..it just iterates through the array and outputs the results:
The Classes:
using System;
using System.Diagnostics;
namespace KnowDotNet.VirtualMethods
{
  
public enum ComputerType{Desktop, Laptop, Server, PDA};
  
/// <summary>
  /// Summary description for Computer.
  ///
  public class Computer
   {
      
private string brand;
      
private ComputerType computerStyle;
      
public Computer(string brand)
      {
//Be a good OOP programmer and use the Accessor
        this.Brand = brand;
      }
      
public Computer(string brand, ComputerType computerStyle)
      {
        
this.Brand = brand;
        
this.ComputerStyle = computerStyle;
      }
      
public string Brand
      {
        
get{return brand;}
        
set{brand = value;}
      }
      
public ComputerType ComputerStyle
      {
        
get{return computerStyle;}
        
set{computerStyle = value;}
      }
      
public void ShowDetails()
      {
          Debug.WriteLine("Computer Brand: " +
this.Brand);
          Debug.WriteLine("Computer Style: " +
this.ComputerStyle.ToString());
      }
   }
  
class DataBaseServer : Computer
   {
      
public DataBaseServer(string brand) : base(brand)
      {
      }
      
public DataBaseServer(string brand, ComputerType computerStyle) : base(brand, computerStyle){}
      
public new void ShowDetails()
      {
          Debug.WriteLine("DataBaseServer Brand: " +
this.Brand);
          Debug.WriteLine("DataBaseServer Style: " +
this.ComputerStyle.ToString());
      }
   }

  
class WebServer: Computer
   {
      
public WebServer(string brand) : base(brand){}
      
public WebServer(string brand, ComputerType computerStyle) : base(brand, computerStyle){}
      
public new void ShowDetails()
      {
          Debug.WriteLine("Web Server: " +
this.Brand);
          Debug.WriteLine("Web Server: " +
this.ComputerStyle.ToString());
      }
   }
}
Now the invocation for the bad Implementation:
Computer[] ComputerInfo    = new Computer[3];
private void cmdBadTest_Click(object sender, System.EventArgs e)
{
    ComputerInfo[0] =
new DataBaseServer("Dell",ComputerType.Server);
    ComputerInfo[1] =
new WebServer("Hewlett-Packard", ComputerType.Server);
    ComputerInfo[2] =
new Computer("Compaq", ComputerType.Desktop);
    ShowDetails();
}
public void ShowDetails()
{
  
for(int x =0; x < ComputerInfo.GetLength(0); x++)<BR>    {
       ComputerInfo[x].ShowDetails();
   }
}

Now here are the results:
Computer Brand: Dell
Computer Style: Server
Computer Brand: Hewlett-Packard
Computer Style: Server
Computer Brand: Compaq
Computer Style: Desktop

Notice that even though I'm adding an instance of the derived class to the array, when we iterate through it and call its
ShowDetails method, the Base Classes implementation is what's being called!  I guess I could still say that I've implemented 'real' Polymorphism here, but my 'real' implementation doesn't work, at least not like we'd expect it to.  After all, we can the derived class' implementation, we aren't looking for the base class' implementation.  To fix this I  just copied these classes, added the suffix Good to the end of each one and made two modifications.  First, I added the Virtual  modifier.  The C# spec provides some good documentation on the Virtual modifier and the first part of it is even kind of funny.  Basically a method is a Virtual Method if it has the Virtual modifier in its signature.  There's a little more to it than that and if you aren't familiar with Virtual methods, I encourage you to check it out.  Anyway, after adding the Virtual modifier, I need to modify the base classes.  Instead of using new I use the Override modifier.  In a nutshell, the override modifier gives the method a new implementation and 'overrides' the base class' implementation.
So now we have the following definitions:
using System;
using System.Diagnostics;
namespace KnowDotNet.VirtualMethods
{  

  public class ComputerGood
   {
      
private string brand;
      
private ComputerType computerStyle;
      
public ComputerGood(string brand)
      {
        
//Be a good OOP programmer and use the Accessor
        this.Brand = brand;
      }
      
public ComputerGood(string brand, ComputerType computerStyle)
      {
        
this.Brand = brand;
        
this.ComputerStyle = computerStyle;
      }
      
public string Brand
      {
        
get{return brand;}
        
set{brand = value;}
      }
      
public ComputerType ComputerStyle
      {
        
get{return computerStyle;}
        
set{computerStyle = value;}
      }
      
public virtual void ShowDetails()
      {
          Debug.WriteLine("Computer Brand: " +
this.Brand);
          Debug.WriteLine("Computer Style: " +
this.ComputerStyle.ToString());
      }
   }
  
class DataBaseServerGood : ComputerGood
   {
      
public DataBaseServerGood(string brand) : base(brand)
      {
      }
      
public DataBaseServerGood(string brand, ComputerType computerStyle) : base(brand, computerStyle){}
      
public override void ShowDetails()
      {
          Debug.WriteLine("DataBaseServer Brand: " +
this.Brand);
          Debug.WriteLine("DataBaseServer Style: " +
this.ComputerStyle.ToString());
      }
   }
  
class WebServerGood: ComputerGood
   {
      
public WebServerGood(string brand) : base(brand){}
      
public WebServerGood(string brand, ComputerType computerStyle) : base(brand, computerStyle){}
      
public override void ShowDetails()
      {
          Debug.WriteLine("Web Server: " +
this.Brand);
          Debug.WriteLine("Web Server: " +
this.ComputerStyle.ToString());
      }
   }
}

We'll use the following invocation to call those methods:

ComputerGood[] ComputerInfoGood = new ComputerGood[3];
private void btnGoodTest_Click(object sender, System.EventArgs e)
{
    ComputerInfoGood[0] =
new DataBaseServerGood("Dell",ComputerType.Server);
    ComputerInfoGood[1] =
new WebServerGood("Hewlett-Packard", ComputerType.Server);
    ComputerInfoGood[2] =
new ComputerGood("Compaq", ComputerType.Desktop);
    ShowDetailsGood();
}
public void ShowDetailsGood()
{
  
for(int x= 0; x < ComputerInfoGood.GetLength(0); x++)<BR>    {
       ComputerInfoGood[x].ShowDetails();
   }
}

And we'll see the results that we wanted all along:
DataBaseServer Brand: Dell
DataBaseServer Style: Server
Web Server: Hewlett-Packard
Web Server: Server
Computer Brand: Compaq
Computer Style: Desktop

Everything now works as planned and we know that we have a working implementation.
  



Writing Add-Ins for Visual Studio .NET
Writing Add-ins for Visual Studio .NET
by Les Smith
Apress Publishing