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
to disambiguate the cases
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.
switch statements, in particular, are
notoriously easy to
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
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
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.