CSC/ECE 517 Fall 2007/wiki2 8 42

From Expertiza_Wiki
Jump to navigation Jump to search

Author: Matthew B. Simmons


Objective

The objective of this document is to present the concepts of method overloading and method overriding. In doing so, it will contrast how method overriding is handled in common object-oriented programming languages (such as C#, Java or C++) and in multimethod languages.

Overloading

Method overloading occurs when there are two methods within the scope of a class that have the same name but different parameter list (and therefore different signatures). The scope of a class is not necessarily limited to the file that defines it, but it will also include methods from each superclass up the inheritance hierarchy.


The principal becomes a little more complicated when one considers the method in which the compiler uses to determine which overloaded method to activate when parameters involve subtypes. Consider the following partial class definitions taken from a national dental practice management system. In it there are Tooth objects and PerioTooth objects, which are a subclass of Tooth objects. There are also Restorations objects and PerioSplint objects, which are children of Restorations. The syntax is in C#.


public class Tooth
{
    public bool AddRestoration(Restoration restoration) { }
}

public class PerioTooth : Tooth
{
    public bool AddRestoration(PerioSplint restoration) { }
}


PerioTooth now has two overloaded methods AddRestoration() that add either a normal Restoration (the first definition) or a PerioSplint, which is specific to periodontal work (the second definition). They are both within the same class scope of PerioTooth. Consider the following:


Tooth tooth = new Tooth();
PerioTooth perioTooth = new PerioTooth();
Tooth ptTooth = new PerioTooth();

tooth.AddRestoration(new Restoration(Restorations.Amalgam, "MO", 32);
perioTooth.AddRestoration(new Restoration(Restorations.Composite, "O", 3);
ptTooth.AddRestoration(new PerioSplint("4-6");


When the compiler sees calls to AddRestoration(), it first checks the type of the receiver of the call -- which is determined by the object type followed by the variable name when the object is instantiated. For example, Tooth in "Tooth tooth = new Tooth()" or PerioTooth in "PerioTooth tooth = new PerioTooth()." From there it begins looking for a signature match by using the types of the arguments.


Method overloading requires caution. A method should not be overloaded if it and the newly created method do not mirror functionality almost exactly. It would be counterintuitive to overload one AddRestoration() to add a Restoration object to a tooth and another AddRestoration() to determine the current temperature in Guatemala!

Overriding

Where overloading was creating methods with the same name and different arguments (and therefore a different signature), overriding occurs when a method replaces another method further up the inheritance hierarchy that has the same signature. For example, consider the following classes:


public class ImageButton
{
    // in C#, the keyword virtual indicates that a method can be overridden
    public virtual void Paint(Graphics graphics, EventArgs e) { }
}

public class ToggleButton : ImageButton
{
    // in C#, overriding is an EXPLICIT action by using the keyword override
    public override void Paint(Graphics graphics, EventArgs e) { }
}


The ToggleButton class inherits directly from the ImageButton class. ToggleButton has a method Paint() that overrides the Paint() method of ImageButton that was inherited. Notice that C# above mandates explicit overriding (the use of the keyword override) -- other languages such as Java allow for implicit overriding, where methods with the same signature automatically override their inherited methods without the use of an override keyword.


One major advantage of overriding methods is that you get polymorphic behavior. For example, if a List object was created and populated as...


List<ImageButton> buttons = new List<ImageButton>();

buttons.add(new ImageButton());
buttons.add(new ToggleButton());
buttons.add(new ImageButton());


...then polymorphism allows the following actions with the displayed results (in C# syntax):


foreach (ImageButton button in buttons)
    button.Paint(this.CreateGraphics(), new EventArgs());

// iteration 1 -- calls ImageButton's Paint()
// iteration 2 -- calls ToggleButton's Paint()
// iteration 3 -- calls ImageButton's Paint()


This dynamic method invocation (the decision about which method to call being made at runtime) is the polymorphic behavior that was produced from overriding the Paint() method.


Whereas overloading resolution was a function of the compiler, overriding resolution is a function of the runtime environment. With overloading, any decisions about which method to call was based first on the type the object was declared to be (for example, Tooth in Tooth ptTooth = new PerioTooth()). Overriding uses dynamic method invocation to look at the actual class type (PerioTooth in the aforementioned example) to determine which override to call. In the most common object-oriented languages, the types of the arguments are not considered.


CLOS (see-loss or kloss) and Multi-Method Language Overriding

Common List Object System (CLOS) is a dynamic object oriented language that is an extension of Common Lisp. It is a multi-method language. A multi-method language is distinguishable from non multi-method languages by the decision mechanism by which overridden methods are chosen at runtime.


In more common object oriented languages (like Java or C#), the runtime decision about which overloaded is made is based on the type of the object being sent the message (dynamic method invocation illustrated above) and NOT the type of the arguments to that message. At compile time, the type of the arguments is used to determine an appropriate method in the set of overloaded methods that match a given signature, but this action is strictly a strategy of the compiler to check for valid code. At runtime, the type of the arguments play no role.


For example, consider the following example in C#:


public class Person
{
    public string name = string.Empty;
}

public class User : Person { }
public class Administrator : Person { }
public class Maintainer : Person { }

public class FormatPrint 
{
    public void Print(User person) { MessageBox.Show("User"); }
    public void Print(Administrator person) { MessageBox.Show("Administrator"); }
    public void Print(Maintainer person) { MessageBox.Show("Maintainer"); }
}


The following code will not run using C# because it is unable to decide which Print() method to call!


FormatPrint formatPrint = new FormatPrint();
Person person = new Administrator();
print.Print(person);


C# is unable to determine which method to call because it is unable to figure out what person really is because it is an argument -- the fact that it is an Administrator is ignored!


On the other hand, a multi-method language such as CLOS considers the argument types at runtime and will determine which Print() method to call. In the above example, languages of this type would decide that since the person argument is of type Administrator, FormatPrint::Print(Administrator) will be selected and executed at runtime. The effect is that the code is "smarter" in such a way that it's flexibility is increased with the ability to differentiate receivers given this higher degree of information. For example, the following LISP example illustrates two methods that could be properly decided at runtime because of the argument types:


(defmethod equals-to((x ficus) (y rose)))
(defmethod equals-to((x rose) (y ficus)))


Contrast that with the decision algorithm of a non-multi-method language at runtime and the increased flexibility is clear.

(NOTE: Although the LISP example presented does illustrate the power of the multi-method runtime decision methodology, it is considered bad form to overload methods by switching the order of the arguments. In practice, this should never be done.


C# and Non Multi-Method Language Overriding

Unlike CLOS, C# (and other popular object oriented languages like Java) is not a multi-method language so the decision about which overridden method to call at runtime is based on the type of the object being sent the message. Consider the following modification to the above code:

public class Person 
{
    public string name = string.Empty;
    public virtual void Print() { };
}

public class User : Person
{
    public override void Print() { MessageBox.Show("User"); }
}

public class Administrator : Person
{
    public override void Print() { MessageBox.Show("Administrator"); }
}

public class Maintainer : Person
{
    public override void Print() { MessageBox.Show("Maintainer"); }
}


Note that the following code decides which overridden method to call based on the type of argument before the message Print().


List<Person> people = new List<Person>();
people.Add(new Administrator());
people.Add(new Maintainer());
people.Add(new User());

// prints "Administrator"
people[0].Print();

// prints "Maintainer"
people[1].Print();

// prints "User"
people[2].Print();


Summary

Overloading happens when methods in the scope of a class share the name but differ in the type of arguments passed. The methods do not share a common signature. Conversely, overriding a method happens when the method a method has the exact signature of one that the class inherited -- in which case the new method replaces the old one. Multi-method languages differ from non-multi-method languages in how they decide which of the method to invoke. Multi-method languages use the type of the arguments whereas non multi-method languages use the type of the object being sent the message.

Reference Summary

The following references were used in the text of or creation of this document: