Saturday, June 20, 2009

Failure To Grasp Polymorphism

I've cataloged a third specific case of fundamental failures to understand polymorphism. The first two I've seen a fair number of times. The third seems to be more rare.

1. "How do I determine which subclass an object has?" The Identification problem.

2. "How do I morph an object to a different subclass?" The Transmutation problem.

3. "I can do that with delegation, I don't need subclasses." The Denial problem.

Identification

The Identification problem is the most common. There are two variants: People ask about class comparisons, and people who use some other value as a surrogate class comparison. Either way, they have if statements scattered around the code.

Bad.

if someObject.__class__ == ThisClass:
someObject.this_foo_method()
elif someObject.__class__ == ThatClass:
someObject.that_foo_method()

Worse.

if someOtherIndicator == "this":
someObject.this_foo_method()
elif someOtherIndicator == "that":
someObject.that_foo_method()

Better. Use inheritance. Override one method, don't provide two.

someObject.foo_method()

Transmutation

This is more subtle because there's no easy "wrong" implementation. Instead of bad code, you have goofy questions.

For example:
Both of these are attempts to "dynamically" transmute an object from one class into another.
There are two variants: people ask about having the superclass morph into a subclass, or people want to make a class change so that the object's behavior changes.

In the morph case, they've overlooked the essential truth of inheritance. Every subclass object is an instance of the superclass, too. If you think you want to transmute from superclass down to subclass, that's silly because the subclass object already is an instance of the superclass. By definition. If you think you want to morph, you really want some kind of Factory that spits out proper subclass instances.

In the state-change case, they've overlooked the power of delegation and the Strategy pattern. If you think you want to use a class change, you really want to plug in a different strategy object.

Denial


The example is great. It proves that you don't need inheritance. Sadly, the proof only works if you're overriding every method. If you don't want to override every method, then inheritance suddenly becomes useful.

The denial problem (all delegation, no inheritance) is a kind of opposite to the transmutation problem (all inheritance, no delegation).