Two Patterns for Polymorphism

Joseph Bergin

Pace University
jbergin@pace.edu

This paper contains some patterns for dynamic polymorphism in Java primarily. The Polymorphism primary pattern gives hints on what to do during design of your program when you are thinking about the object in your program and the services they will provide. The Polymorphism secondary pattern is more useful when you are modifying an existing program that uses ad-hoc polymorphism (selection) to make decisions.

Polymorphism (primary)

You are thinking about the services that your program must provide. You discover that one service might be provided in more than one way. Different clients may require different versions, but generally a given client will always require the same version.

How can we provide several versions of the same service cleanly and efficiently?

If you provide several different methods to provide the versions of this service, you may need to write if statements (selection) to choose between them. This is a poor solution as these if statements tend to proliferate through the program, making modifications difficult and error prone. If you try to put all of the versions into the same method, then you will need to use selection in this method to distinguish between the different clients. This is a poor solution if we may want to add new classes of clients, as these if statements will need to be rewritten.

Therefore, consider having distinct objects provide the different versions of the service. Whenever possible the service should have the same name and the same parameters in each of these objects. Each client will be connected to an object that implements the appropriate version of the service.

The different objects providing the services will come from different classes. Each class will provide for one version of the service. If it seems appropriate, each of these classes can be subclasses of a common (abstract) class in which the service is defined as an abstract method. Alternatively, this common super class can provide the default version of the service with subclasses providing less common alternatives. If a small inheritance tree does not seem advisable, have each of your server classes implement the same interface in which the service is defined.

Then, when you create the clients of this service, connect each client to an object that will provide the version of the service required by that client.

A service with the above properties can be called a polymorphic service.

Then, to add a new version of the service, just write a new class deriving from some existing class in your hierarchy or implementing the same interface. To add a new kind of client, just see that these new clients get connected to the appropriate version of the service. Only the newly written class (service version, or client) needs to be modified. Existing classes and methods remain unchanged. This aids long term maintenance of the program.

If a given client must use different versions of the service at different times then you should provide a mechanism such as a mutator method by which the client can learn of its new server. This is preferable to using if statements to test to see which version should now be used with this client. See the secondary form of Polymorphism to understand why. Advanced mechanisms for maintaining links between clients and servers include hash tables in which we can look up the server implementing the currently required version.

For example, a database server may need to use a local database for some clients and a national database for others. If both servers implement the same interface or derive from the same (abstract) parent server class then we have a polymorphic service. Then, if clients can each be connected to the appropriate version, no testing will be required in the program to have the appropriate service used.


 

Polymorphism (secondary)

You are writing a method and have come to a place where you need to make a choice. Exactly one of several possibilities is appropriate at this point and you must choose between them. Doing nothing at all may be one of the choices.

How can we make a choice cleanly and efficiently?

In many circumstances selection may be appropriately used to make the choice. However, you may have a situation where the choice of what to do depends on the type (formal) or kind (informal) of thing that you are processing. If you use selection here, the same selection structure will be likely to appear elsewhere in your program where the same decision about type or kind occurs. This is called ad-hoc polymorphism and it is difficult to maintain, since you need to find all of the if statements that implement it and change them when changes occur to the program.

Therefore, consider doing a redesign of the program at this point. This is technically called refactoring. In changing your program design, consider primary polymorphism. Try to design the various individual actions that you want to perform here as a single polymorphic service for the clients of the various types you are considering.

If you can do this, then, for example

if(someThing instanceof A) {serverForA.doAThing(something);}
else if (something instanceof B) {serverForB.doBThing(something);}
else …

becomes something like:

someThing.doTheRightThing();


To apply this in general may require rethinking your clients as well as your services. This is because the client will need to be connected to the appropriate server. Perhaps your clients can be subclasses of a single parent class, or can implement the same interface.

This pattern does not require type (instanceof) checking as in the above example. Sometimes you have two things of the same type (perhaps) that are logically different in some way. For example, you may need to do one thing when a collection is empty and a different one when the same collection is not empty. Ad-hoc polymorphism has you checking whether the collection is empty or not, usually in several places. You may be able to arrange it so that you have a special object to represent empty collections and a different object is used to represent nonempty collections. If these can be built in different classes (derived from the same root, or implementing the same interface) then the different actions can be implemented in a service of these classes, avoiding the ad-hoc polymorphism.

A good example of this is the Null-Object Pattern (Bobby Woolf) which suggests that you use a special object to terminate things like linked lists. To use this pattern for a List, you have an abstract class called ListNode with two concrete subclasses, NonEmptyNode and EmptyNode. They have the same interface as they both derive from ListNode. The EmptyNode class has no fields. The NonEmptyNode class has fields for the value stored and for the reference to the next node. The methods of EmptyNode (the class of the null object) don’t do anything. In particular they don’t pass any messages to "next". Every list is terminated with a null object, usually the same object, for all lists (see Singleton). Used well, the List class will need NO if statements in its implementation. You can also design algorithms on the List that themselves don’t need if statements. (See the Visitor Pattern). We can then get the following kinds of transformations.

if(list.empty()){…doEmptyThing()…}
else {…doNonEmptyThing()…}
becomes
list.doTheRightThing();
Or
if(node == null) {…doNullThing()…}
else {…doNonNullThing()…}
becomes
node.doTheRightThing();

Notes: This type of polymorphism is difficult or impossible to achieve if the tests need to be done on primitive values (not objects) such as int, since these values don’t have polymorphic methods. It is also difficult to achieve when the decisions need to be made based on inputs from a human user or when the decisions need to be make to determine what type of object to create. If you can’t apply either of these polymorphism patterns, apply the selection patterns instead.


Example

LispwVisitor.java is a Java class that exhibits a high degree of polymorphism by using the Null Object, Singleton, and Visitor patterns. Null Object, in particular, permits us to avoid all selection testing to determine the end position in the list.

Associated with this is an example visitor class that illustrates usage.


Discussion

In addition to having a high degree of polymorphism in your program you also want to Say It Once. In fact, this is the reason that you don't want the if statements in the first place: they are usually repeated. If you can Say It Once you have a single point of change for updates of most kind.

Suppose you have been applying our primary pattern here and you now have five service classes. Each of them performs the same service, but in different ways. Suppose each service requires more than one method. However, you discover that one of these methods must be implemented one way in three of the classes and a different way in the other two. How do you Say It Once? You don't want to write the same code more than once in these classes.

The solution is the Strategy pattern described in the patterns literature, for example in Design Patterns by Gamma, Helm, Johnson, and Vlissides. This pattern applies when you have a basic algorithm that always executes in the same way, but requires specialization at one or a few points.

In this case do the following. I will use abstract names here. In a real case make it more specific, of course. Create an interface for this one aspect of the service (the one that has two implementations). It has one method, called perform, that has the same parameters as the method with multiple implementations.

interface ServiceStrategy
{	void perform(....);
} 

Now, implement this with concrete classes once for each of the distinct versions of your service.

Then create these objects and give an instance of one of them to each of your five service classes to use when this method is to be executed. In effect the five services defer or delegate the performance of this part of the service to the new object.

In a complex situation in which additional information is needed beyond the parameters of the service itself, it can be provided either through additional parameters to perform or through parameters passed to the constructor of the ServiceStrategy objects or other means.

The same idea here, using a Strategy, can also be used when applying secondary Polymorphism when you are trying to think how to do it. Delegating to a strategy object is often a good way, but not the only way and not always the best way. A study of Design Patterns can help you here.


There are a number of papers on this site that show the difference between ad-hoc and dynamic polymorphism and explain why the second is more desirable. They provide additional examples as well. See

http://csis.pace.edu/~bergin/patterns/ppoop.html

http://csis.pace.edu/~bergin/patterns/persongender.html

http://csis.pace.edu/~bergin/patterns/strategydecorator.html

In many ways this is the essence of object-oriented programming.


Last updated: April 17, 2002 4:19 PM