Friday, April 3, 2009

Polymorphism

Polymorphism

Polymorphism is a simple concept that you understand right now, as it is prevalent through life. You are a person, which person is a base type and you are a more specific representation of that type. There are many people that inherit that type, of which you are a concrete implementation of that type: you have your own beliefs, attributes, and qualities that go beyond just a general person. For instance, we all have a blood type, shoe size, arm length, and many other properties. But we each add our own implementation to the person interface.

You drive an automobile, which this type has properties like wheel size, engine size, gas tank size, and other properties. The automobile you drive is a concrete implementation of the automobile interface. It has additional features, like sliding doors, logo type, cd changer slot count, moon roof lever location, or other various properties that are specific to the make/model of the car. In addition, automobile may have certain behaviors like open/close door, open trunk, turn wheel, and other behaviors that would be specific to an automobile.

In OO programming, using the automobile example, Automobile would be the base class, and each automobile manufacturer would have its own implementation. For instance, Honda has V-Tec technology, which is in its own implementation. Volvo uses diesel engines, which is the TDI technology. More importantly, you may add an added level of detail between automobile and the make/model implementation, such as Car, Truck, or Suv supertypes, to provide more relevant information.

Let's take a look at the automobile scenario. Here is an example automobile class:

  1. public abstract class Automobile  
  2. {  
  3.   //Properties  
  4.   public int DoorCount { .. }  
  5.   public string EngineType { .. }  
  6.   public float Height { .. }  
  7.   public float GasTankSize { .. }  
  8.   public float WheelBase { .. }  
  9.   public float WheelSize { .. }  
  10.   
  11.   //Abstract Properties  
  12.   public abstract string BodyType { get; }  
  13.   
  14.   //Methods  
  15.   public float CalculateFuelEfficiency(int lastMileage);  
  16. }  

The Automobile class is the interface, in that it defines an interface that classes will derive from. You can't directly instantiate it through new Automobile() because it's not an implementation; however, you can cast derived types to the Automobile type. In this sense, it defines a common interface across many derived implementations.

It's important to note the BodyType property. This property is declared abstract, meaning that any derived implementation must override it and return some relevant string (we will see more of this with the next class definitions). This is similar to the Template Method pattern in that it exposes properties/methods that derived classes must override, but which Automobile doesn't need to know anything about. Automobile wants the derived class to return something relevant to it, but doesn't care what.

Now, from the example above, let's say we want a higher level of abstraction, in that there are varying types of cars and we want to target that. It is possible for classes to inherit from Automobile, but to also be an interface in that it can't be directly instantiated, but any class can inherit additional properties/methods from it. Below are two such class definitions, for Car and Suv.

  1. public abstract class Car : Automobile  
  2. {  
  3.   //Properties  
  4.   public bool HasSpoiler { .. }  
  5.   public bool IsHatchback { .. }  
  6.   
  7.   //Overridden Properties (abstract)  
  8.   public override string BodyType { get { return "Car"; } }  
  9.   
  10.   //Abstract properties  
  11.   public abstract bool IsFast { get; }  
  12. }  
  13.   
  14. public abstract class Suv : Automobile  
  15. {  
  16.   public bool CanOffroad { .. }  
  17.   public int TowingCapacity { .. }  
  18.   
  19.   public override string BodyType { get { return "SUV"; } }  
  20. }  

Notice the BodyType method is overridden to return a string that represents the type of body that's appropriate to the supertype. Also notice that Car and Suv also expose their own properties/methods, even abstractly in terms of the Car.IsFast property. Lastly, let's create an implementation of the Car class:

  1. public class SubaruWrx : Car  
  2. {  
  3.   public override bool IsFast  
  4.   {  
  5.     get { return true; }  
  6.   }  
  7. }  

The SubaruWrx class can use any of the properties defined in Automobile and Car. It has DoorCountEngineType, etc. properties that are available, because it inherits from Car, which Car inherits from Automobile. You can cast SubaruWrx to Car or to Automobile, because it inherits from these base classes, and adds its own properties/methods. However, say you cast SubaruWrx to Automobile; none of the properties/methods defined in SubaruWrx or Car will be available, because all this object knows is that it's of type Automobile. To gain access to those properties/methods, you must upcast to the appropriate type.

There are several ways that polymorphism can take place. The first is through class inheritance, which you see above. Class inheritance exposes the interface of the base class to all of the derived classes. Another option is interface inheritance. The interface declares a contract that the class must follow to implement the interface. In addition to class inheritance, an object can inherit from one or more interfaces, which adds properties/methods to the body of the class that it must provide implementation for. This is another common option that can be used in .NET development. Some languages offer the ability to inherit from multiple base classes; however, .NET is not one of those languages.

Examples of Polymorphism

There are many examples of Polymorphism in the .NET framework. One of them is the Membership provider. When you call Membership.GetUser or any other method, it calls the default provider, which is defined as a MembershipProvider class. Any derivatives (SqlMembershipProvider or other custom providers) expose the MembershipProvider interface to create a concrete implementation. You can easily switch the underlying data store without having to change any code for the Membership object.

When you work with a typed dataset, the base objects for the tables/rows are inherited from DataTable and DataRow. These objects can be downcast to this base type. When downcast to this type, only the properties/methods defined in DataTable/DataRow can be used, eliminating the strong-typing that comes from a typed dataset.

The GridView and DetailsView web controls allow you to create custom fields, by creating a class that inherits from DataControlField. Because of this, you do not have to create your own custom GridView/DetailsView classes to expose new field types; you simply create a new field that inherits from DataControlField, and create a custom implementation by overriding the base class's ExtractValuesFromCell and InitializeCell methods.

0 comments:

Post a Comment