Logo of the Augustana Faculty, University of Alberta

COMPUTING SCIENCE 220

Software Engineering and Human-Computer Interfaces


Using Polymorphism Instead of Conditional Logic: An Example



The textbook provides an example of the use of polymorphism to avoid the use of conditional logic (which tends to be hard to change because it ends up being scattered throughout the application).

Here is another example. I assigned the datastructures and algorithms class the task of writing an expression evaluator that would convert an infix expression to postfix and then evaluate the postfix expression to return a result (a double). I wanted to provide an Operator class which, given a string (such as "abs" or "sqrt") or a single-character symbol (such as '+' or '*'), would return an Operator object that knew about the characteristics of the operator associated with that string or symbol, such as its precedence, arity, and associativity, and that would perform the correct mathematical operation when provided with the correct number of operands.

My first attempt used the new enumeration types offered by Java 5, but used condition logic in order to decide which eval() method should be invoked for each operator: a no-argument eval, a single-argument (unary) eval, a two-argument (binary) eval, or a three-argument (ternary) eval. The class constructs a table of Operator objects on initialization, so each Operator object is a singleton. Each object knows about the characteristics of the operator it represents, but each object contains all four variants of the eval method and executes the correct mathematical operation only by the use of case logic.

. Operator.java using case logic [Don't use this version.]

It took a combination of new features of Java 5 in order to substitute polymorphism for case logic in the Operator class. (To be precise, the new version is an enum, not a class per se, but the new Java enumeration type has all the power of a regular class, but organized according to enumeration constants.)

First, it uses the feature of the new Java enum that a static constructor is invoked for each enumeration constant (effectively creating a separate object corresponding to each enumeration constant) if one provides a constructor and follows the declaration of each enumeration constant with a parenthesized list of parameters to be passed to the constructor. This replaces the static table of Operator objects used in the previous version of the class.

Second, it uses another feature of the new enumeration type: if a method of the enum is declared abstract, a concrete implementation of that method can be provided for each enumeration "object" (each enumeration constant) in a block (enclosed in curly braces) following the declaration of each enumeration constant.

It required another new feature of Java 5 to make the eval() method truly polymorphic: the "varargs" facility. This feature — the ability to declare methods as accepting a variable number of arguments of some type — has long been available in languages such as C, and is a welcome addition to Java (if only because it also allowed Java 5 to offer the printf function so valued by C programmers and available in many subsequent languages). By declaring the eval method as

   double eval(double... args)

a client class (such as the postfix expression evaluator) can call eval with any number of arguments (preferably, with the correct number for the particular operator to be invoked) and the eval method will do the right thing (i.e., either apply the appropriate operator to the operands provided or throw an exception indicating that the wrong number of arguments were supplied).

The final result is a fully polymorphic eval method:

. Operator.java using a polymorphic eval method

The implementation of the static operator() method illustrates one more feature of the Java enumeration type: the new "foreach" statement can be used to iterate across the enumeration constants, because a values() method is provided by default, returning an iterable collection of the enumeration constants declared in the enclosing enum

   for (Operator op : Operator.values())

Note that other enumeration types can be nested in the top-level enum. In this case, Arity, Associativity, and Location are simple enumerations, declaring only enumeration constants, without associated behavior.

Copyright © 2005 Jonathan Mohr