Wednesday, December 5, 2012

Inheritance, Interfaces, and Abstraction in Two Dimensions

Previously, we explored the benefits of using interfaces in object oriented programming, noting that the key benefit was polymorphism that's detached from inheritance trees. Today I wanted to take a quick look at the relationship of inheritance and interfaces from another angle.

Both inheritance and interfaces are ways to achieve abstraction, but they have different trade-offs, and it's good to know what they are, so that when you're designing your software, you use the right tool for the job.

The Disadvantage of Inheritance

As we saw previously, inheritance has a particularly crippling disadvantage -- once you commit to a particular inheritance tree, you're basically stuck with it. Sure, you can extend the tree by adding subclasses or even super classes. But you're still locking yourself into that particular inheritance line.

Extending an inheritance tree with subclasses and super classes

Two Dimensions of Abstraction

When we use inheritance as it was meant to be used, we're creating an abstraction of essence. All I mean by that is that, as we follow the inheritance tree downward -- that is, as we subclass -- we're creating a more specific version the object we're modeling. In other words, every subclass should, by definition, be its superclass -- a VenusFlyTrap is a Plant.

Interfaces, on the other hand, give you the ability to abstract in a second dimension that's free from the inheritance tree. You can still use it to achieve abstraction of essence like you can with inheritance. But you can also use it to achieve an abstraction of capability. This is why you'll frequently see interfaces with names like ISortable, ISerializable, and IEnumerable -- these names all express capabilities.

Two dimensions of abstraction -- essence and capability.

In this diagram, BugEater is an interface. Eating bugs is a capability of a VenusFlyTrap. Being a Plant, on the other hand, is the essence of a VenusFlyTrap. By the way, there's no reason why LivingBeing or Plant couldn't also be an interface. Again, interfaces provide abstraction in two dimensions, but inheritance is designed to provide abstraction in only one.

Of course, if you really want to use inheritance to express an abstraction of capability, it's entirely possible to do so. In your system, you might choose to do that if, for example, a BugEater class would need to share implementation with subclasses, but LivingBeing and Plant do not. Or, if LivingBeing and Plant did not exist. Generally, though, it's best to reserve inheritance for an abstraction of essence.

Guidelines

So when should we use inheritance, and when should we use interfaces? Here are some basic guidelines that I use when designing abstractions.

  1. You only get one inheritance relationship, so make it count.
  2. Inheritance should be used to express a relationship exhibiting essence. A VenusFlyTrap is by essence a Plant.
  3. Interfaces can also be used to express essence. If you don't have to share any implementation details with your subclasses, start with an interface. For example, if you don't need Plant to include code that both VenusFlyTrap and PeaceLily inherit, then make Plant an interface.
  4. Prefer interfaces for expressing capabilities. Names that end in -er (e.g., BugEater) and names that end in -able (e.g., Sortable) could be clues that you're expressing capabilities rather than essence.

No comments:

Post a Comment

Profile Picture
Dave Leeds
My Hobbies:
  • Programming
  • Cartooning
  • Music Writing
Full Profile