Tuesday, January 13, 2009
Virtual vs. Abstract
So, I know I'm going back to Object Oriented 101, but I've forgotten how powerful certain keywords can actually be. That is, until recently.
I was asked to build a product that would be flexible enough to kiss it's own behind, but easy enough to maintain that non-programmer-types could mangle out a few lines of code to keep extending this. (i.e. engineers).
Yeah. That's what everyone wants, right? And the general answer is usually "and people in hell want water." But this time, I think I've actually found a solution to one of their issues.
Enter "VIRTUAL". It's a subtle change, but one that's Oh-so-important in this case.
Let's say we have an object called circle. That object has a method called GetRadius(). GetRadius does exactly what it sounds like. It returns the distance from the center of the circle to the edge.
Now, if we create a new object that inherits circle, let's call this one doughnut...mmmmmmmmm.... doughnuts... the term GetRadius becomes a bit more tricky. Are we talking about the inner circle or the outer circle? Lets say that we want it to be the difference between the two.
So, how do circles apply to the issue of kissing your own behind? Let me show you.
Library 1 contains our first object; Circle.
Now, if we want to extend this library by creating new DLLs that inherit the Circle class, we can still add them to CircleCollection because they are, in fact, a type of circle. So if someone creates a new DLL called Doughnut, we might have:
Since DOughnut is a type of Circle, we can add it to the previously-defined CircleCollection class with no problem.
But let's take a look at the results of the numbers. We've defined doughnut the same, whether it is abstract or virtual, but the numbers will be different depending on HOW we ask the program to reference the object... as a Circle or as a Doughnut.
When I loop through all of the elements in my CircleCollection, as Circles, we get some interesting results. Let's loop through a few and get the radius. First well get it by Virtual, then we'll get it again by abstract.
You would think the answer for the left number would be the same as the right, but they are different for doughnuts. The Virtual will always be whatever the deepest answer in the tree is. In this case, doughnut was the deepest in the heirarchy.
However, the abstract version will always be the answer from the requested type, in this case Circle (note: foreach circle...).
The first number for each of the "doughnut" objects used the code within the doughnut class, while the second number used the code within the circle class. THAT's the difference between Abstract and Virtual.
So, back to the engineers. As long as they inherit their new objects from my base type, all my libraries (which use "virtual") can reference the items in the collection by their base type.
Since all of my base types are collections of other types (i.e. EngineeringObjectCollection) I can loop through my collections as I normally do. If they have created their own types and override the functionality of one of my methods, then I'll use their code. If not, I'll use mine. No extra coding on my part is required. No messy reflection. No fuss, no muss.
And just like that, the code can now kiss it's own behind... well, provided the engineers can access the "clsBehind" object with the prgObject.Kiss() method. But you get the idea.
Now, all this talk about doughnuts has made me hungry.
I was asked to build a product that would be flexible enough to kiss it's own behind, but easy enough to maintain that non-programmer-types could mangle out a few lines of code to keep extending this. (i.e. engineers).
Yeah. That's what everyone wants, right? And the general answer is usually "and people in hell want water." But this time, I think I've actually found a solution to one of their issues.
Enter "VIRTUAL". It's a subtle change, but one that's Oh-so-important in this case.
Let's say we have an object called circle. That object has a method called GetRadius(). GetRadius does exactly what it sounds like. It returns the distance from the center of the circle to the edge.
Now, if we create a new object that inherits circle, let's call this one doughnut...mmmmmmmmm.... doughnuts... the term GetRadius becomes a bit more tricky. Are we talking about the inner circle or the outer circle? Lets say that we want it to be the difference between the two.
So, how do circles apply to the issue of kissing your own behind? Let me show you.
Library 1 contains our first object; Circle.
public class Circle
{
public double diameter { get; set; }
public Circle (double OuterD)
{
diameter = OuterD;
}
public virtual double GetRadius_Virtual()
{
return diamter / 2;
}
public abstract double GetRadius_Abstract()
{
return diameter / 2;
}
}
public class CircleCollection : List<Circle>
{
}
Now, if we want to extend this library by creating new DLLs that inherit the Circle class, we can still add them to CircleCollection because they are, in fact, a type of circle. So if someone creates a new DLL called Doughnut, we might have:
public class Doughnut : Circle
{
public double innerDiameter { get; set; }
public Doughnut (double OuterD, double InnerD)
{
diameter = OuterD;
innerDiameter = InnerD;
}
public override double GetRadius_Virtual()
{
return (diameter - innerDiameter) / 2;
}
public override double GetRaidus_Abstract()
{
return (diameter - innerDiameter) / 2;
}
}
Since DOughnut is a type of Circle, we can add it to the previously-defined CircleCollection class with no problem.
But let's take a look at the results of the numbers. We've defined doughnut the same, whether it is abstract or virtual, but the numbers will be different depending on HOW we ask the program to reference the object... as a Circle or as a Doughnut.
When I loop through all of the elements in my CircleCollection, as Circles, we get some interesting results. Let's loop through a few and get the radius. First well get it by Virtual, then we'll get it again by abstract.
CircleCollection C = new CircleCollection();
C.Add(new Doughnut(10,8));
C.Add(new Doughnut(9:5));
C.Add(new Circle(10));
C.Add(new Circle(9));
foreach (Circle circ in C)
{
Console.WriteLine(circ.GetRadius_Virtual() + ":" + circ.GetRadius_Abstract());
}
You would think the answer for the left number would be the same as the right, but they are different for doughnuts. The Virtual will always be whatever the deepest answer in the tree is. In this case, doughnut was the deepest in the heirarchy.
However, the abstract version will always be the answer from the requested type, in this case Circle (note: foreach circle...).
Output:
1:5 <--- doughnut
2:4.5 <--- doughnut
5:5 <--- Circle
4.5:4.5 <- Circle
The first number for each of the "doughnut" objects used the code within the doughnut class, while the second number used the code within the circle class. THAT's the difference between Abstract and Virtual.
So, back to the engineers. As long as they inherit their new objects from my base type, all my libraries (which use "virtual") can reference the items in the collection by their base type.
Since all of my base types are collections of other types (i.e. EngineeringObjectCollection) I can loop through my collections as I normally do. If they have created their own types and override the functionality of one of my methods, then I'll use their code. If not, I'll use mine. No extra coding on my part is required. No messy reflection. No fuss, no muss.
And just like that, the code can now kiss it's own behind... well, provided the engineers can access the "clsBehind" object with the prgObject.Kiss() method. But you get the idea.
Now, all this talk about doughnuts has made me hungry.
Subscribe to Posts [Atom]
Post a Comment