13. Polymorphism

Part of CS:2820 Object Oriented Software Development Notes, Fall 2015
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

A More Complex Model

The code we produced last Monday works, but it is wrong because we've overlooked a huge item that makes real traffic models more complex. Specifically, there are multiple kinds of intersections. We have at least the following variants:

We could refine this further, adding stop signs as an attribute of roads:

We could also introduce intersections that are "all way stop sign" intersections. In that case, connecting a road to that intersection should automatically promote it to a road with a stop sign.

Similarly, in the neural net example, we have several kinds of synapses, and in a logic simulator, several kinds of gates.

Impact on the Road Network Descriptioin Language

This has an immediate impact on our road network description. Where we formerly just said:

intersection A
intersection B

We can now say things like:

intersection A stoplight
intersection B

We've made a decision above, a decision that has several consequences. That is, for specialized types of intersection, we explicitly name the intersection type, but there is also a default where there is no explicit name. We could have reqired intersection B above to be declared as a plain intersection or something like that. The primary problem with this design decision is that it complicates the problem of parsing the input file.

Polymorphism

Java supports polymorphic classes, that is, classes where there are multiple possible implementations. In fact, polymorphism was added to Simula '67, the original object-oriented programming language, precisely to allow for the kind of variation discussed above.

Specifically, Java (and its ancestor Simula '67) allows us to introduce new classes that extend an existing class. For example, we could have:

/** Intersections with no stop light
 *  @see Intersection
 */
class NoStop extends Intersection {
}

/** Intersections with a stop light
 *  @see Intersection
 */
class StopLight extends Intersection {
}

Wherever it is legal to have an Intersection, it is now legal to have either a NoStop or a StopLight. It is legal to assign a NoStop or a StopLight to an Intersection, but you can only assign an Intersection to a StopLight, for example, with care.

How do these new subclasses differ from the parent class? The simplest place they differ is in the toString method, so we can immediately create new methods for that:

/** Intersections with no stop light
 *  @see Intersection
 */
class NoStop extends Intersection {
	return (
		"Intersection " +
		name
	);
}

/** Intersections with a stop light
 *  @see Intersection
 */
class StopLight extends Intersection {
	return (
		"Intersection " +
		name +
		" Stoplight"
	);
}

In the above, except for capitalization, we've made the output of the toString() method recreate our input text.

Note that, wherever it is legal to have an Intersection, it is now legal to have either a NoStop or a StopLight. Thus, if we have the following declarations in a hypothetical bit of code,

Intersection i;
NoStop       n;
StopLight    s;
The assignments i=n and i=s are legal. It is also legal to say i=new NoStop() or i=new StopLight(). In the opposite direction, you cannot be so free. n=i is illegal — what you have to write if you want this is n=(NoStop)i which means "check to see that i is actually a NoStop and then, if it is, do the assignment; if it isn't, throw an exception."

Initializers for Polymorphic Objects

When you create a new object, you must pick its actual class. Once an object is created, you cannot change its class. So, we must change the code to initialize intersections. Here is the old code for initializeNetwork()

        static void initializeNetwork( Scanner sc ) {
		while (sc.hasNext()) {
			String command = sc.next();
			if (("intersection".equals( command ))
			||  ("i".equals( command ))) {
				inters.add( new Intersection( sc, inters ) );
			} else if (("road".equals( command ))
			||         ("r".equals( command ))) {
				roads.add( new Road( sc, inters ) );
			} else {
				Errors.warning( "unknown command" );
				// Bug:  should we allow comments?
			}
		}
	}

We need to change the part for initializing intersections. This raises a problem. Our input language is arranged like this:

intersection X stoplight

Here is an alternative syntax:

intersection stoplight X

If we had done the latter, we could have learned that we were about to initialize a member of class StopLight before we knew its name, so we could call the initializer and let the initializer pick up the name, as it does in our current version of the code. Now, however, we must scan over the name first before we discover the type. So, we will have to write code like this:

                        String command = sc.next();
                        if (("intersection".equals( command ))
                        ||  ("i".equals( command ))) {
                                // Bug:  What if no sc.next?
                                String name = sc.next();
                                // Bug:  What if no sc.next?
                                String kind = sc.next();
                                if (kind == "stoplight") {
                                        inters.add( new StopLight( sc, name ) );
                                } else {
					// Bug:  How do we undo sc.next()?
                                        inters.add( new NoStop( sc, name ) );
                                }
                        } else if (("road".equals( command ))

Note that in our old code, we had called, new Intersection(sc,inters). We passed the list inters as a parameter to the initializer early in the design process because we thought it would be needed to look up the intersection name. Then later, we introduced the findIntersection() method that did not need this list as a parameter because findIntersection() is a method of class RoadNetwork, where inters can be accessed directly.

So, when we introduced findIntersection() we should have eliminated this parameter. Instead, we can do it now. But, in the new code, we have to pass the name of the new intersection, and as the bug notices indicate, we have some serious problems. How do we detect that there's nothing left on the input line in order to detect a missing name or a missing kind? This code will not work as written.