Wednesday, May 14, 2008

Why Inheritance Is Bad

3D graphics programmers, if there are any that read this blog, are familiar with gimbal lock. The Wikipedia article makes an analogy to compass directions. If you tell somebody at the north pole to face south, which direction are they facing? You have no way of knowing, because south loses its meaning at the north pole. At any other place on the planet, your location and direction are completely independent. At the poles, direction has no meaning in the cartographic coordinate system. Two previously independent variables have now become linked.

In programming, when you derive from a base class, the classes are in a state of gimbal lock. We can vary the subclass as much as we want without affecting the base class. However, a change to the base class will not only affect our subclass, but every other subclass out there. Suppose that our base class provides some core data processing functionality, and its various subclasses encapsulate different data sources. So far, so good. We've managed to avoid duplication. If we come up with a new data source, we can simply create a new subclass. However, what happens if we want an existing data source to send data to a different set of processing rules? A class would need to have more than one base class, except in a way different from normal multiple inheritance. No programming language that I've seen can support this scenario.

A hallmark of good object-oriented design is that classes have one and only one responsibility. Why is this important? It makes the code simple, it makes the code testable, it decreases coupling, it might increase cohesion, and it's just plain nicer to read. When you derive from a base class, your class has all of its explicit responsibilities, and the responsibilities of each of its ancestor classes. In our example, each subclass is itself responsible for knowing the ins and outs of a particular data source. Since they all derive from a common base class, they are also responsible for knowing how to process the data. This example only deals with 2 responsibilities, but real systems deal with hundreds.

Remember that inheritance is often used to represent taxonomies. You might have classes such as Animal > Mammal > Giraffe or Widget > Button > Pushbutton. You might say that things on the right are defined in terms of things on the left, but that's not exactly true. A giraffe doesn't have lungs because it's a mammal; we call it a mammal because it has lungs. Using inheritance to describe something (using, for example, interface inheritance) makes a lot of sense. Using inheritance to define something doesn't.

5 comments:

Anonymous said...

> However, what happens if we want an existing data source to send data to a different set of processing rules? A class would need to have more than one base class, except in a way different from normal multiple inheritance. No programming language that I've seen can support this scenario.

My least favorite thing about object oriented programming is inheritance, a sentence that seems like it cuts at the heart of OOP, yet it seems to me that it rarely provides the cleanest, most maintainable, and extendable way to solve problems. Unfortunately, I see too many solutions force fit into an inheritance hierarchy. I think the answer to the problem you describe is mixins.

At a high level, what you would do in a language like Java to solve your problem is to create strategies for the data processing rules and inject them at construction time into the class the programmer will end up using. You might likewise turn the data source into a strategy pattern, ending up with one class whose role is to facilitate communication with data processors and data sources.

This is mildly painful (but way better than the alternatives in Java), though not quite what you want. What you want is to give the programmer the same flexibility to specify what data processing strategy to use with what data source strategy, and to do so as close to type or instance declaration time as possible.

In C++, instead of injecting the strategies at runtime, you can flip the hierarchy on its head and have the former "base" facilitator class inherit from the strategies (or policies) you wish to use. You then enable an easy way of selecting the policy by turning this facilitator class into a template class to allow the developer at instance declaration time to choose which policies he'd like to use. e.g.

template <class DataSourcePolicy, class ProcessPolicy>
class Data
: DataSourcePolicy, // provides GetData()
ProcessPolicy // provides ProcessData()
{
public:
void DoStuffWithData(...)
{
ProcessData(GetData(...));
}
};

class MyDataSource
{
public:
char *GetData()
{
return "data";
}
};

class MyDataProcessor
{
public:
void ProcessData(char *data)
{
// do lots of important things
std::cout << data << std::endl;
}
};

//...

Data<MyDataSource, MyDataProcessor> myData;
Data<YourDataSource, YourDataProcessor> yourData;
// etc...

You can get even fancier with template template types should you need your main class and its policies to act on a particular, yet generic, type of data. See Modern C++ Design for more on this.

In Ruby, you define modules that implement your various strategies for doing stuff and inject them into the facilitator class. Ruby gives a lot of flexibility in how you wish to do this; I believe you can mix in modules per instance, or for the entire class, and you can have the module mixin when it is included, or manually do it in select places.
http://www.juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby/ might get you started.

The neat thing is that mixins provide another tool in the inversion of control toolbox, and make strategy patterns closer to first class citizens of the language, which is a great thing as I tend to see most "design patterns" as language workarounds.

- Marc

Daniel Yankowsky said...

Using the example form my post (the Scriptaculous Autocompleter), there are at least 3 responsibilities - the responsibility to produce the data, the responsibility to show an interface to the user, and the responsibility to respond to input from the user. I didn't mean for that to be an MVC breakdown, but it's pretty close. Anyway, if we put those three responsibilities in a Pyrex dish and bake it in the oven, what will happen? Nothing will happen. Those three things are just pieces. In order for them to work together, they need some supervision (code isn't exactly self-organizing yet - it's not sentient enough). I believe that the supervision code belongs in a separate place, orthogonal to the other three.

I don't quite know how to apply mixins to this situation. I guess you create one Organizer class that mixes in nothing (and isn't yet complete), and create one subclass for every combination that you use. C++ style templates would be great, but Ruby doesn't support them, and I don't know whether its metaprogramming magic could be used here (though it probably could).

On the other hand, if you use simple constructor injection, you could always create utility methods that equip your Organizer with the proper strategies. I guess it becomes an aesthetic choice - do you prefer classes or functions as your general-purpose tool?

Marc said...

That's exactly right, the facilitator (as I called it, which can be a controller) helps traffic the input data to the output. It sounds like the fourth responsibility, the supervision code you are referring to, is the thing that makes sure the right three guys are in place. This is either accomplished at a language level (e.g. mixins) or is injected via some dependancy injection manager (Guice/Spring) or the programmer, which instantiates the right classes at the right time. Ultimately, the goal is to avoid being locked into a particular implementation of an algorithm/datastore/view/whatever in a way that is easy to extend, easy to test, and (most importantly) easy to use.

Ruby has more that enough power to handle this. My impression is that Ruby's mixins seem a bit sloppier than C++, but are a whole lot easier to reason about and get right the first time.

Anonymous said...

You said:
If you tell somebody at the north pole to face south, which direction are they facing?

They are facing south because that is what you just told them to do.

Daniel Yankowsky said...

You're correct that they are facing in the direction that I told them to face. My point is more that the position that they are facing is ambiguous. Are they looking out over the eastern hemisphere, the western hemisphere, or right down the prime meridian? Or maybe they're looking at the ground, because the south pole is down there somewhere.