CSC/ECE 517 Fall 2009/wiki2 4 railroad
Student: Manhattan
Introduction
Polymorphism is one of the fundamental strengths of object oriented systems and design. Polymorphism can not only make code more closely mirror the “real” world, it can also help make code more elegant and maintainable. One way polymorphism can help make code more elegant and maintainable is by reducing the number of conditional statements. Conditional statements are statements in a programming language that perform different actions based on the current state of a variable or object. Examples of conditional statements include the “if” statement and the “switch” statement. This concept of “Replacing Conditional with Polymorphism” is a commonly accepted refactoring technique. This article explores several examples of why replacing conditional statements with polymorphism is desirable.
When Conditional Statements Are Useful
In large software systems, conditional statements can make code less modular and require that more lines of code be updated when a change needs to be made. However, before beginning our discussion of why conditional statements are bad, let's look at a simple example of when they are useful and appropriate.
Let’s say we have a system that allows for management of train stations. Perhaps, a snippet of code from the system for selecting a station might look like:
if(users_selection == “PHL”) { /* Station = Philadelphia, PA */ } else if(users_selection == “NYC”) { /* Station = New York, NY */ } else if(user_selection == “PVD”) { /* Station = Providence, RI */ } else if(user_selection == “RDU”) { /* Station = Raleigh, NC */ } ... and so on ...
This code works well. Even when a new station needs to be added, the programmer can simply add another “else if” statement to make the code work. Simple conditional statements, such as the example above, are arguably necessary in every non-trivial program. When encapsulated within a class's method to perform simple comparisons, conditionals provide a very simple and elegant way to add functionality to the system.
Conditionals that Make it Hard to Maintain Code
Arguably, any non-trivial software system will contain some form of complex logic. Let’s say our system also contains a way to get the services the station offers; these services might be: food for travelers, train fuel, connections to local subway trains, etc. If we tried to use conditional statements to do this, we might see something like:
switch( my_station.get_name() ) { case PHL: /* Return things about Philadelphia’s train station */ break; case NYC: /* Return things about New York’s train station */ break; case RDU: /* Return things about Raleigh’s station */ break; case PVD: /* Return things about Providence’s station */ break; ... and so on ... }
This approach will work, but, what if each station has many different types of services? Or, what if certain services are not available at certain stations. That is, what if each station has services, but they are mostly unique to that station. We could probably get around this by creating, and returning, an instance of an STATION_INFO class. However, this code is arguably not very elegant. In one switch statement, there could be many different things happening and it could be very difficult to maintain.
But what if we created a train station base class, then created subclasses that represent each individual train station? We could use polymorphism to always return the correct station information. For example,
TRAIN_STATION[ ] the_stations = new TRAIN_STATION[10]; the_stations[0] = new PHL_STATION; the_stations[1] = new NYC_STATION; the_stations[2] = new RDU_STATION; the_stations[3] = new PVD_STATION;
... and so on ...
Assuming each subclass is required by the TRAIN_STATION superclass to implement a method called ‘get_my_services’, the switch statement in the previously example would simply become:
/* Get Philly’s train station services */ services = the_airports[0]. get_my_services; /* Get New York’s train station services */ services = the_airports[1]. get_my_services; /* Get Raleigh's train station services */ services = the_airports[2]. get_my_services; /* Get Providence’s train station services */ services = the_airports[3]. get_my_services; ... and so on ...
Now, if any of the services need to be modified, updated, or otherwise changed, we can simply go to the specific train station subclass and modify its ‘ get_my_services’ method. The code is very easy to understand.
Conditional Statements Security Risk
Conditional statements can also pose a significant security risk that can be mitigated using polymorphism. Keeping with our train station management system example, let’s assume we have functionally to return a string that indicates the name of the local public transit system that connects to the train station. Again, if we used a conditional, such as a “switch” statement we may have:
switch( my_station.get_transit_service_string() ) { case PHL: public_transit_name = “SEPTA”; break; case NYC: public_transit_name = “MTA”; break; ...and so on ...
Notice that Raleigh and Providence were both left off. Why? Perhaps these cities have public transit systems, but they do not connect to the train station. Perhaps the programmer forgot to put them in. There is an infinite number of reasons why they may have been left off. With a standard conditional statement, there is virtually no way to enforce that all conditions are enumerated. If something such as an uninitiated string or other variable is introduced, it could cause problems in other parts of the system. Of course, in all major languages, a ‘switch’ statement has a ‘default’ case that can be used as a catch-all.
When using polymorphism, this problem can be easily avoided. Java, C++, and Ruby all contain ways to enforce that subclasses implement certain methods or at least allow the base class to “safely” implement the method. In Java, a class’s methods can be declared as ‘abstract’, in C++ they can be declared as virtual, and in Ruby, the ‘method_missing’ method can be used.
For example, let's implement the connecting mass transit string with an abstract method in Java.
/* The Base Class */ class TRAIN_STATION { abstract string get_transit_service_string(void); ... other methods ... }
By declaring the ‘get_transit_service_string’ method as ‘abstract’, we force any classes that inherit from TRAIN_STATION to implement the ‘get_transit_service_string’ method. If not, a compiler error will be generated.
External Resources
The following are a list of external resources that contain more information and examples on polymorphism vs. conditional statements.
[1] An example of an Employee class that calculates salary: http://www.eli.sdsu.edu/courses/spring01/cs635/notes/refactorPatterns/refactorPatterns.html#Heading3
[2] Several Examples of refactoring code to replace conditionals with polymorphism: http://sourcemaking.com/refactoring/replace-conditional-with-polymorphism
[3] Information on "abstract" classes and methods in Java: http://java.sun.com/docs/books/tutorial/java/IandI/abstract.html
[4] Information on Ruby’s ‘method_missing’ method: http://www.rubycentral.com/pickaxe/ref_c_object.html
[5] Information on Virtual methods in several languages: http://en.wikipedia.org/wiki/Virtual_function