Seven deadly sins of programming - Sin #2
One of the guys on my wing during my freshman year of college was a trust-fund kid. His great-grandfather had done well in the market, and had left some money for each the great-grandkids.
Michael wasn't paying for his schooling, nor was he paying for his cars, his clothes, his nice stereo, or his ski vacations. But that was okay with us, because he *was* paying for pizza, and he *was* paying for beer. Or his trust fund was.
Everything was fine until spring came around, and Michael got some bad news. His trust fund had been set up to carry him through 4 years of school, but because he spent money so fast, he had burned through all of it in less than two years. He was forced to get a job at the local grocery store to finish the year out, but couldn't afford tuition and had to leave school and find a job to support himself.
It typically shows up in chapter two or three of the programming book. There's a section titled something like, "What is an object?", which speaks in flowing terms about the wonderful work of object-oriented development, and uses one of the following examples:
- Geometric shapes
- Musical instruments
- Cephalopods (rare)
In this section, we find out that a square is an example of a polygon, a cheetah is a cat and also a mammal, and so on.
All of this to introduce is to the concept of "is-a".
We then see an example where we can ask any polygon how much area it covers, tell any mammal to walk, and tell any cat to ignore us while we're talking to it, all through the wonders of inheritance and virtual methods.
Good taste and a firm grasp of the relative usefullness of this concept would dictate spending 10 or 20 pages explaining these concepts, but most texts significantly exceed that, and most programming assignments spend a fair bit of time on that as well. Kindof like how linked lists rule your life for a month or so.
It's therefore not surprising that many younger developers think that inheritance is a feature that you should, like, "use" when you write software.
There are a few problems with this.
The first is that "is-a" is, in my experience, a pretty rare relationship between objects. More common is the "looks the same on one dimension but has different behavior across another dimension".
Good luck implementing this:
public class Monotreme: Mammal
public override GiveBirth()
// add appropriate implementation here
That's the sort of thing that tends to be non-obvious when you first create an object, but annoying obvious when you've bought into the whole inheritance mindset.
That's not to say that inheritance isn't useful. It's just to say that you should understand that a MemoryStream isn't really a Stream, at least in the true "is-a" sense, and be prepared to deal with it.
The second problem is more philosophical and aesthetic. I recently wrote code like this:
class ClimbList: List<Climb>
Which seems like a perfectly reasonable thing to do, at least on the surface.
But the surface - or at least the surface area - is a problem. List<T> is a pretty extensive class, and when I defined ClimbList, I was saying that ClimbList does the proper thing when any of the List<T> methods or properties are called.
I'm pretty sure that's not true. Or at least, I'm not at all sure that it *is* true (I have no tests to support such a belief), but users of ClimbList don't have any way of knowing that the only methods I'm currently using are Add() and the indexer. Intellisense brings up all of them when I go to call one of the methods I wrote.
Which brings us in a long and roundabout way to our penultimate sin:
Sin #2 - Overuse of Inheritance
So, what should you use if you don't use inheritance (and I am advocating that you approach it carefully and thoughtfully - it's a similar decision to adding a virtual method)?
Composition. Make the object a field inside of your object, and then write the forwarders that you need (it's usually not more than one or two).
And, if you really need inheritance in your design, add it carefully and thoughtfully.