My struggles in understanding and learning about Object Oriented design, and the tools and knowledge I've taken from them.

Sunday, October 12, 2008

Abstraction, Abstract Classes, and Interfaces

One of my struggles in figuring out OOP has been getting a grasp on the concept of ABSTRACTION. ABSTRACTION is a fundamental concept that differentiates OOP from procedural programming, because it cannot be used if there is no concept of classes and objects.

It's been my experience that there are 2 main ways of implementing abstraction in OOP: ABSTRACT CLASSES and INTERFACES.

Each of the above has their own advantages and implementations, and they can certainly overlap, but I would say that the chief difference between ABSTRACT CLASSES and INTERFACES is that, by definition, when a class inherits from an ABSTRACT CLASS, the inheriting class has a "IS-A" relationship with the ABSTRACT CLASS, and it also has a "CAN-DO" relationship with the ABSTRACT CLASS.

A class that implements an INTERFACE simply has a "CAN-DO" relationship with the interface it is implementing, in that the methods in the class that implements an INTERFACE must correspond to the methods defined in the interface (same name, same parameters, etc).
Another way of describing the difference between an ABSTRACT CLASS and an INTERFACE is that an ABSTRACT CLASS could possibly have an implementation, but an INTERFACE does not (by the way, when I say that something could or could not have an implementation, what I mean is that it can or cannot be instantiated as an object).

I'll give a code example, so I can describe these differences better. Remember that an ABSTRACT CLASS and an INTERFACE can be interchangable at times, so I'll give an example where this is true. Consider the oldie-but-goodie example of a shape. The concept of a shape is abstract, in that a shape could mean many things (does the shape have sides? How many sides? What is the area of the shape? How does the shape get drawn?). Consider the following:

abstract class Shape
{
   ///Notice I'm not implementing anything
   ///with this method. It's basically a
   ///shopping list item
   public abstract void Draw();
}

///The class Square inherits from
///the abstract class Shape
public class Square: Shape
{
   ///Notice I use the keyword override
   ///I'm overriding the non-implemented
   ///method Draw() that is inherited
   ///from the Shape abstract class
   public override void Draw()
   {
      ///Do stuff that draws the square
   }
}

Notice how Square inherits from the Shape class. The effect of this is that the Shape class gives some structure to the concept of actual shapes in your application. There may be a dozen or so classes that implement Shape (Square, Circle, Rectangle, Triangle, etc). The value in doing this falls into a concept that I have come to find very important in OOP: one class shouldn't know too much about the world.

You may look at that statement and think that I have an overly-sentimental attachment to code, but I think it's very important, especially if you'll be working on the same code base for a long time. One class should serve 1-and-only-1 purpose. If a class starts to do too many things, you lose the versatility that OOP affords, and it simply becomes procedural. More on fundamental concepts like these later.

INTERFACES
INTERFACES are a little different than ABSTRACT CLASSES, in that INTERFACES do not have an implementation. In the above example, the abstract class was not necessarily an implementable class (because the only method in it was abstract), but that is not always necessarily true that an abstract class can have no implementation. INTERFACES never have an implementation, and that has advantages and disadvantages. The main disadvantage is that it is a little slower than an ABSTRACT CLASS. One advantage is that it's a little purer of an abstraction. When the rubber meets the road, ABSTRACT CLASSES and INTERFACES are used in combination to get the best of both worlds.

An INTERFACE would have just about the same syntax as the above example, except for the keywords:


Interface IShape
{
   ///Notice I'm not implementing anything
   ///with this method. It's basically a
   ///shopping list item
   public abstract void Draw();
}

///The class Square inherits from
///the Interface Shape
public class Square: IShape
{
   ///Notice I don't use the keyword
   /// override. I don't have to
   ///override, because interfaces
   ///have no implementation to
   ///override
   ///In this definition, interfaces
   ///simply provide a "contract"
   ///between classes that implement them
   public void Draw()
   {
      ///Do stuff that draws the square
   }
}

I'll have to be honest, in my own experience, I tend to use interfaces more than abstract classes, simply because I feel more comfortable with them, but both interfaces and abstract classes provide functionality that is vitally important:

1. They create a contract between classes that implement them. This is important if there are multiple people working on different parts of the application, and a bridge needs to be built between different people's work. (BTW, I tend to use the phrase "shopping list" to describe these non-implementable pieces. Because it really is just a list of things a class "CAN-DO.")
2. It creates the ability to reuse a lot of code, because you can take advantage of the "is" keyword in many OOP languages, such as C#, and this falls into another axiom in programming: Reusability is important. The more you can reuse a method, the less likely it is to break code down the line.

Speaking of #2 above, consider the following example:

public void Draw(IShape shape)
{
   ///Notice that I use the "is"
   ///keyword here. I've been
   ///told this is reminiscent
   ///of a "factory" design pattern
   ///in that I can use if or switch
   ///statements to add bunches
   ///of logic to this method to
   ///make it very reusable
   if(shape is Circle)
   {
      ///Do stuff from Circle class
      ///to draw the circle
   }
   
   else if(shape is Square)
   {
      ///Do stuff from Square class
      ///to draw the square
   }
}

I find myself doing the above type of thing a lot, and I'm quite ok with that, because I just pass in some object that implements the specified interface (in this case, IShape), and let the method do the processing for me.

Digg It
Post a Comment

Followers

Search This Blog

Powered by Blogger.