B669: Personalized Data Mining and Mapping
Don't Use Fortune Cookies

 

Two groups of programmers wish to come to some agreement about how to communicate with each other by passing objects. For example, suppose that an interface wants to notify a user model about interface events. The question at hand is this: should the modelers require the interfacers to produce one class, InterfaceEvent, which takes a string (or, worse yet, some predefined magic numbers) in its constructor to specify what kind of event it was ("Entity Opened", "Entity Selected", "Entity Moved", and so on) or should they have one superclass, InterfaceEvent, and a number of subclasses (EntityOpenedInterfaceEvent, EntitySelectedInterfaceEvent, EntityMovedInterfaceEvent, and so forth)?

The first solution is your typical C programmer's trick. It can only lead to wailing and gnashing of teeth in future. The second solution is a polymorphic Java programmer's neat hack. Here's why:

If you go the string route (or magic number route, which is really the same thing) then all the code that is concerned with InterfaceEvents forever and always is going to need the equivalent of a switch statement to disambiguate the cases (disguising the switch with if-else-ifs changes nothing). The class of the object, InterfaceEvent, is not particularly useful to you. It is simply a carrier for the true nugget of information---the string or magic number inside it, stored there during its construction. It's a fortune cookie.

Such code is notoriously hard to extend since any time you want to make slight changes in InterfaceEvents (for example to add a new one) you have to go through all the code (both to produce InterfaceEvents and to consume InterfaceEvents) and modify it. And switch statements, in particular, are notoriously easy to break.

It's a maintenance nightmare. It's the typical dreadful code that C hackers around the world have to deal with day after day and is one of the leading causes of that notorious malady: Code Bugginess.

Instead, let's see what polymorphism gives us.

Suppose we defined a number of subclasses of InterfaceEvent, say EntityOpenedInterfaceEvent, EntitySelectedInterfaceEvent, EntityMovedInterfaceEvent, and so on. Let's give InterfaceEvent a method, say, report() (choose your own more descriptive name depending on the actual application). In each of the subclasses we override report() and do something specific for that subclass.

To use these objects we simply pass in the object as its superclass type, InterfaceEvent, and ask it to report() itself. Polymorphism then does the entire job for us that previously we had to use buggy switch statements for.

What are the gains?

* We no longer have to care what the actual subtype of the InterfaceEvent object is (nor do we even have to care if it has more than one subtype).
 
* If we decide tomorrow to add a new InterfaceEvent (say, ScreenOnFireInterfaceEvent) all we have to do is create a new subclass of InterfaceEvent and write its report() method. We don't have to change the handler code at all.
 
* The compiler takes care of matching types to code for us, so there can never be a mistake in future.

Here is the secret of object-oriented programming: instead of using objects to simply carry state and putting all the object-manipulating smarts in handler code (which then has to change if the object's state changes) as in the fortune-cookie solution, the object itself also carries its own behavior (in this case, the report() method) that gives it enough intelligence for it itself to figure out how to handle itself depending on its state. Adding new objects, or modifying old objects, is then a snap.

Using objects only to carry state is a lose.

Note that using the instanceof operator here doesn't make a whole lotta sense either since that's just a slightly more sophisticated version of the same old switch statement solution sketched above. It has the same maintenance problems. Further, as part of adding reflection, Java 1.1 added a dynamic version of the (static) instanceof operator called the isInstance() method; it leads to similar problems with mindless use. Code that uses switch statements (or if-else-ifs) and/or instanceof in place of polymorphism may look like Java, but it's not really: it's C. Never use either of them if you are really just checking for every single subtype of some type.