- The Class Hierarchy
- Creating a New Class
- Declaration and Instantiation
- Constructors and Destructors
- Garbage Collection
- Inheritance
- Object Operators
- Adding and Overriding Methods
- Calling the Overridden Method
- Overloading
- Casting
- Oddities
- Encapsulation
- Access Scope: Public, Private, Protected
- Setting Properties with Methods
- Default and Optional Parameters
- Declaring Variables Static and Const
- Revisiting the StringParser Module
- Example: Creating a Properties Class
- Data-Oriented Classes and Visual Basic Data Types
- Advanced Techniques
Oddities
When you subclass a class that has overloaded methods, if the subclass overloads methods, the superclass doesn't know about them. So if you instantiate the subclass but assign it to a variable as the superclass, the overloaded method can't be called from it (this contrasts with how overridden methods are handled, because you can call an overridden method in the same way and get the subclass implementation of it).
I'll provide an example of one way that really confused me for a few days until I realized what I was doing wrong.
An overloaded method is one that takes different arguments (but has the same name). An overridden method has the same signature (takes the same arguments), but is implemented differently. This applies to inheritance between a class and a subclass. An overloaded method is overloaded in the same class, and overridden method is overridden in a subclass.
Start with two classes, Parent and Child. Set the parent of the Child class to Parent. For both classes, implement a method called WhoAmI and one called GetName() as follows:
Parent:
Sub WhoAmI(aParent as Parent) MsgBox "Parent.WhoAmI(aParent as Parent) /" + _ "Parameter: " + aParent.getName() End Sub Function getName() as String Return "Parent" End Function
Child:
Sub WhoAmI(aChild as Child) MsgBox "Child.WhoAmI(aChild as Child) /" + _ "Parameter: " + aChild.getName() End Sub Function getName() as String Return "Child" End Function
The MsgBox subroutine will cause a small Window to appear, displaying the string passed to it.
Next, take a look at the following code. The text that would appear in the MsgBox is listed in a comment next to the method.
Dim p as Parent Dim c as Child Dim o as Parent p = New Parent c = New Child o = New Child p.WhoAmI p // "Parent.WhoAmI : Parent.getName" p.WhoAmI c // "Parent.WhoAmI : Child.getName" c.WhoAmI p // "Parent.WhoAmI : Parent.getName" c.WhoAmI c // "Child.WhoAmI : Child.getName" o.WhoAmI p // "Parent.WhoAmI : Parent.getName" o.WhoAmI c // "Parent.WhoAmI : Child.getName" o.WhoAmI o // "Parent.WhoAmI : Child.getName" Child(o).WhoAmI p // "Parent.WhoAmI : Parent.getName" Child(o).WhoAmI c // "Child.WhoAmI : Child.getName" Child(o).WhoAmI o // "Parent.WhoAmI : Child.getName"
The left side of the response shows which class implemented the WhoAmI method that was just called, and the right side of the response shows which class implemented the getName() that was called on the object passed as an argument to the WhoAmI method.
The first two examples show the Parent object p calling the WhoAmI method. In the first example, a Parent object is passed as the argument for WhoAmI and in the second example, a Child instance is passed as the argument. Both examples act as you would expect them to. The p object always uses the methods implemented in the Parent class and the c object uses a method implemented in the Child class.
The next group of examples are the same as the first two, with one important difference: A Child instance is calling WhoAmI instead of a Parent instance. There's something strange about the results, however:
c.WhoAmI p // "Parent.WhoAmI : Parent.getName" c.WhoAmI c // "Child.WhoAmI : Child.getName"
If c is a Child object, why do the results show that c called the Parent class implementation of the WhoAmI method? Why does it call the Child class implementation of WhoAmI in the second example?
The answer is that Child.WhoAmI() does not override Parent.WhoAmI(). It overloads it. Remember that to override a method, the signatures have to be the same. When Child implements WhoAmI, the parameter is defined as a Child object, but when Parent implements it, the parameter is defined as a Parent. REALbasic decides which version of the overloaded method to call based on the signature. What is tricky here is that it is easy to forget that WhoAmI is overloaded only in the Child class, not in the Parent class, so when a Parent object is passed as an argument, REALbasic uses the Parent class implementation of WhoAmI. However, the c object has access to the methods of the Parent class because it is a subclass of the Parent class.
The rest of the examples work on the object o, which is in a unique situation. It was declared to be a variable in the Parent class, but when it was instantiated, it was instantiated as a Child and this makes for some interesting results. The first three sets of responses all show that o is calling the Parent class version of the WhoAmI method:
o.WhoAmI p // "Parent.WhoAmI : Parent.getName" o.WhoAmI c // "Parent.WhoAmI : Child.getName" o.WhoAmI o // "Parent.WhoAmI : Child.getName"
The getName() method is where things get interesting. As expected, when the argument is an instance of the Parent class, the Parent.getName() method is executed; likewise, if it is a member of the Child class. But when you pass o as the argument to WhoAmI, o calls the Child implementation of getName(), rather than the Parent implementation.
This seems odd because when o calls WhoAmI, it calls the Parent class method, but when it calls getName(), it calls the Child class method. Because o was declared as a member of the Parent class, it is cast as a member of the Parent class, even though you used the Child class Constructor. In fact, when you cast o as a Child, you get an entirely different set of results:
Child(o).WhoAmI p // "Parent.WhoAmI : Parent.getName" Child(o).WhoAmI c // "Child.WhoAmI : Child.getName" Child(o).WhoAmI o // "Parent.WhoAmI : Child.getName" Child(o).WhoAmI child(o) // "Child.WhoAmI : Child.getName"
In the first of this quartet, you get the expected answer because p is a Parent instance, and that means that regardless of whether o is a Child or a Parent, REALbasic will call the Parent class implementation of WhoAmI. In the second example of this group, after o is cast as a Child, it behaves as expected, calling the Child class version of WhoAmI.
However, when o is passed as the argument to WhoAmI, it doesn't matter that o has been cast as a Child, it again calls the Parent.WhoAmI method. On the other hand, if you also cast the o object passed in the argument as a Child as well, things again are as they should be. So the question is why does this happen:
Child(o).WhoAmI o // "Parent.WhoAmI : Child.getName"
The implementation of WhoAmI that is used is determined by the type of the object passed in the parameter. The implementation of getName() is not determined by any parameter because it doesn't require a parameter. You can also do the following and get the same result:
Child(o).WhoAmI parent(o) // "Parent.WhoAmI : Child.getName"
The getName method is overridden, not overloaded. Because o was instantiated as a Child object, it will always call the Child implementation of getName. WhoAmI is overloaded, not overridden, and it is overloaded only in the Child class and not the Parent class.