Friday, April 15, 2011

Overriding and Overloading

Two terms that sound similar and are sometimes confused are overriding and overloading.  The former is an essential part of inheritance, and therefore of object-oriented programming; the latter is a syntactic device that adds no expressive power to the language.

Overriding a method means providing a new implementation for it in an inheriting class.  This may be necessary because the subclass has a different representation, which has more information than the superclass.  For example, suppose you are programming an interactive game, in which characters lose health points as a result of exertion or by attacks by other players.  This is implemented through a method damage(int points), which inflicts the given amount of damage.  Now assume that you create a special kind of character, called Merlin, which can make itself invisible as well as operate a force shield that protects it from certain kinds of enemy actions.  However, it can't perform these new functions when its health is too low.  The class implementing the Merlin character inherits from the general character class.  It needs to override the implementation of the damage method, in order to disable invisibility and the force shield if the health level drops below the relevant thresholds.  When the damage method is called for any character, it decreases its health; for Merlin, it may additionally disable his special abilities.

The choice of which code runs for a particular invocation of the damage method is made at runtime.  If the character happens to be Merlin (that is, its type is the Merlin class), the code executed will come from the Merlin class.  If the character's type is another class, the code will be taken from that class, or from the closest ancestor class that provides an implementation of that method.  This is called dynamic dispatch, and is a cornerstone of object-oriented programming.

Merlin's special abilities can be damaged by special types of attacks, which do not otherwise damage his overall health.  It seems natural to add to Merlin's class a new method, damage(Ability ability), which damages the given ability but does not affect other abilities or the health measure.  This is a case of overloading: using the same name for two different methods.  Many, but not all, object-oriented languages support overloading.  Java, C#, and C++ all do, while Eiffel doesn't.  The choice of which code gets executed in a call to the overloaded damage method is made by the compiler: if the argument is an int, the general damage method will be called; if the argument is an Ability (that is, its type is the Ability class), the second will be called.  These types must be known at compile time; if they aren't, the program won't compile.

Because the decision of which overloaded method to call is static, it is known to the programmer writing the call.  Therefore, overloading can always be replaced by renaming methods.  In our example, the new method that damages Merlin's special abilities can be called damageAbility to remove the overloading.  In fact, overloaded methods are implemented using this technique; languages such as C++, Java, and C# have a special notion of method name, which attaches encoded information about argument types to the method name as defined by the programmer.

When used properly, overloading can make programs more understandable.  For example, the + operator is overloaded in Java to represent addition of integers as well as floating-point numbers.  That's perfectly understandable, since the mathematical addition operator applies to all these types and has the same properties for them (associativity, commutativity, etc.).  It would be perfectly fine to use + for adding complex numbers if Java allowed this (and is common in C++, which does).  However, Java overloads + to denote concatenation of strings as well.  This is confusing, and is a design error of the language.  This error is compouned by the fact that + coerces its arguments to more general types, so that x + y will denote floating-point addition if x is an int and y is a float, but string concatenation if x as an int and y is a string.  Worse yet, mathematical properties of addition don't hold for string concatenation, and as a result ("a" + 1) + 2 is "a12" while "a" + (1 + 2) is "a3".

Because overloading doesn't add to the expressive power of the language, and is a trap for the unwary, it should be removed from object-oriented languages.  Unfortunately, the C++/Java/C# family of languages encourage overloading, and even require it in the case of constructors, whose names must be the name of the class.  Suppose you want to create a complex-number class called Complex, and give it two constructors, one receiving the rectangular representation of the number (x and y for x+iy) while the other receives the polar representation (rho and theta for rho*e^(i*theta)).  You want to call these two constructors by descriptive names, such as complexFromRectangular and complexFromPolar.  But in C++/Java/C#, both must be called Complex!  Not only are the names non-descriptive, they also coincide, because both constructors take two arguments of the same types.  I won't describe the contortions that programmers of these languages have to go through to solve this problem.

My recommendation: avoid overloading altogether.  If you really feel it's essential in some case, at least make sure that the overloaded methods have the same semantics (as in the case of mathematical addition).  If you use overloading in other cases (such as addition and concatenation), you will have only yourself to blame when you get confused; and your users will be perfectly justified in blaming you when they, in turn, get confused.

No comments:

Post a Comment