CSC/ECE 517 Fall 2009/wiki2 4 va
Topic: If-statements considered harmful, and can be replaced by uses of polymorphism.
Introduction
The If
statement, a conditional language structure, has been available to programmers for quite some time. It was first introduced in FORTRAN in a form of an arithmetic If
statement back in 1957 [1]. The logic behind this language element is simple. The control of the program is redirected to one of three labels, depending on the value of the analyzed arithmetic expression (which could be negative, positive, or zero). Over the years, this language feature had become obsolete and was replaced by a logical If
statement (if-then-else), the one that is still widely used to this day.
The logical If
statement is very familiar to most programmers as it is one of the first language concepts they learn. Despite that, it is a cause of many code errors. Since the If
statement is more suitable for structured programming, most today's object-oriented languages have more sophisticated mechanisms to replace it. One of such approaches is a use of polymorphism.
This paper will discuss ways in which a polymorphism technique can be applied to replace the If
control structures, will talk about pros and cons of polymorphism, and will demonstrate some code examples written in Ruby language.
What is wrong with using If
statements?
Most of the time, a correctly written If
statement would not cause any harm to one's code. It, however, possesses a potential to be error prone, as demonstrated by the following Ruby code:
i = 1 # or any other number if i >= 0 puts "greater or equal to zero" elsif i < 0 puts "less than zero" else puts "will never run!!!!" end
As you can see from this example, the condition for the else part of the statement will never be met. This example does not suggest that a use of If
statements should be avoided. It simply demonstrates a potential in conditional statements to be error prone.
Example with If
statements
Let's consider a simple code that would illustrate a use of If
statement in an object-oriented language. First, we define a parent class, Vehicle
, and two child classes - Car
and Truck
. We would then ask a user to choose which of the two classes they would like to initialize. The If
statement performs a dynamic type checking, and then depending on the user's selection, suggests a type of fuel that would work with the chosen vehicle.
Below is a Ruby code that implements the above utilizing the If
statement:
class Vehicle def initialize puts "I am a vehicle" end end class Car < Vehicle def initialize super puts "I am a passenger car" end end class Truck < Vehicle def initialize super puts "I am a commercial truck" end end class Driver puts "What kind of vehicle do you have?" type = gets # expects Car or Truck, case sensitive my_car = eval(type).new if my_car.class == Car puts "I like 87 octane gasoline" elsif my_car.class == Truck puts "I like diesel" end end
If we respond with Car
, the output would look as follows:
What kind of vehicle do you have? Car I am a vehicle I am a passenger car I like 87 octane gasoline
One apparent downside of this solution is that the code is not easily maintainable if the selection of vehicles
grows further. For instance, let's imagine we decided to add two new Vehicle
types, SportsCar
and Bicycle
. In order to support this addition, we would need to add two new classes, as well as to modify the If
statement.
The updated code of the Driver
class would look as follows:
class Driver puts "What kind of vehicle do you have?" #Expects Car, Truck, SportsCar, or Bicycle, case sensitive type = gets my_car = eval(type).new if my_car.class == Car puts "I like 87 octane gasoline" elsif my_car.class == Truck puts "I like diesel" elsif my_car.class == SportsCar # new class puts "I like 93 octane gasoline" # new output elsif my_car.class == Bicycle # new class puts "I don't need any fuel" # new output end end
Example of If
replaced by polymorphism
Now, let's refactor our code by implementing the same example using polymorphism. The first modification is to introduce a method named get_type_of_fuel
in all child classes. Similar to the earlier example, the caller of the method has no way of knowing which class will be chosen as it would be a run-time decision.
Again, we will use Ruby code to demonstrate the new approach:
class Vehicle def initialize puts "I am a vehicle" end end class Car < Vehicle def initialize super puts "I am a passenger car" end def get_type_of_fuel "I like 87 octane gasoline" end end class Truck < Vehicle def initialize super puts "I am a truck" end def get_type_of_fuel "I like diesel" end end class Driver puts "What kind of vehicle do you have?" type = gets # expects Car or Truck, case sensitive my_car = eval(type).new puts my_car.get_type_of_fuel end
If Car
is selected, the output would remain the same:
What kind of vehicle do you have? Car I am a vehicle I am a passenger car I like 87 octane gasoline
It is important to point out that the maintainability of the application has improved. For instance, as we add new Vehicle
types, the class Driver
would remain completely intact. Thus, the only change would be to add new child classes (and they must contain a get_type_of_fuel
method).
For example, the new class could be defined as follows:
class SportsCar < Vehicle # this is a new class def initialize super puts "I am a sports car" end def get_type_of_fuel "I like 93 octane gasoline" end end
And the most important part is that the Driver
class would remain absolutely unchanged, yet fully support the new addition:
class Driver puts "What kind of vehicle do you have?" type = gets # expects Car, Truck, or SportsCar (newly defined), case sensitive my_car = eval(type).new puts my_car.get_type_of_fuel end
Pros and Cons of Polymorphism
Pros
According to [3], one of the biggest advantages of polymorphism is that new class "features" can be added incrementally. Additionally, these separate class "behaviors" are coded independently from each other, "thus minimizing the possible interference."
Another advantage is that we let "an object figure out how it should behave" [5]. With that in mind, methods should be designed so that they are fully responsible for manipulating object's data which should normally be declared as private
.
Cons
One of the cons of polymorphism is that the "behaviors" are spread among multiple classes (abstractions), as opposed to having everything in one conditional statement.
Another downside is that adding new methods/classes increases the complexity and the size of the code. It is "potentially harder to understand" [3].
Finally, implementing polymorphism in classes takes a lot of preparation and requires more time to be spent up front. Ultimately, the majority of the design decisions are made very early in the development life cycle. That requires a great deal of understanding of the issue that is being solved.
Conclusion
As shown in this article, the use of If
statements can be a cause of uncertainty. In many cases, If
and other conditionals can be successfully transformed to polymorphism, also improving maintainability and flexibility of the application [3].
Links
[1] Arithmetic IF, http://en.wikipedia.org/wiki/Arithmetic_IF
[2] Conditional statements, http://en.wikipedia.org/wiki/If_statement
[3] Transform Conditionals to Polymorphism, http://scg.unibe.ch/archive/papers/Duca00cTransform.pdf
[4] Polymorphism in object-oriented programming, http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming
[5] Polymorphism and Interfaces, http://www.artima.com/objectsandjava/webuscript/PolymorphismInterfaces1.html