WinDev 7.5 dynamically binds a method call to the method in the most derived class of in which the method can be found. That is, the actual class of an object, not the stored class of a reference to an object. If the method cannot be found in the actual class of the object, a search is made back through the parents to the root. However, in the case of searching back through polymorphic classes, only the first defined class is looked at. For example:
C1 is Class END PROCEDURE C1::Display() Info("Class1")
C2 is Class object C1 END PROCEDURE C2::Display() Info("Class2")
C3 is class object C1 END PROCEDURE C3:Display() Info("Class3")
C4 is class object C2 object C3 END
The method display defined in C3 cannot be accessed from anywhere except class C3 or derivations from C3, not even references to C3 whose actual objects are not derived from C3 in the earlies path. For example:
Tmp1 is C4 dynamic = new C4 Tmp3 is C3 dynamic = Tmp1
Tmp1:Display() // This will display "Class 2" Tmp3:Display() // This will also display "Class 2"!
However, if the no reference is found going back to the base class, WinDev will look at the reference class next before searching up from the base class. So for instance, if you remove PROCEDURE C1:Display(), then the results will be different:
Tmp1 is C4 dynamic = new C4 Tmp3 is C3 dynamic = Tmp1
Tmp1:Display() // This will display "Class 2" Tmp3:Display() // This will display "Class 3"
Another issue the has come up with WinDev 7.5 is the removal of the syntax:
MyDerivedClassInstance:MyBaseClass::MyBaseClassMethod()
This permitted the calling of the base class method for an object instance that was not the "object" object. This can no longer be done. Calling a base class method from a derived class can now only be done with the instance object of the class method. I.E. only :MyBaseClass::MyBaseClassMethod() is permitted.
Approaches:
A. Use of Scope Operator ("::")
The most dangerous part of WinDev binding is that a base class in the form of a library cannot execute its own code if a derived class happens to override it. For example, a basic stack class defined as follows:
StackableItem is class Next is object dynamic END
Stack is class TopOfStack is object dynamic END PROCEDURE Stack::Add(AStackableItem) AStackableItem:Next = :TopOfStack :TopOfStack = AStackableItem
would fail to execute if the user of a class (or some derived class way down in the class hierarchy) happened to define a class method call Add. Any attempt to call the Stack::Add would be superceded (even from within Stack).
This problem can be reduced using the scope definition operator "::" from within a base class in order to force correct operation at least from within the class. [This is not presently possible due to a bug in WinDev which prevents you from specifying your own class scope as a class scope method override. However you can define an outer (derived) protecting class which merely relays calls to the inner (parent) class.] This protects the class itself from having its own internal operations inadvertently overridden. For example:
StackableItem is class Next is object dynamic END
InStack is class TopofStack is object dynamic END PROCEDURE InStack::Add(AStackableItem) AStackableItem:Next = :TopOfStack :TopOfStack = AStackableItem
Stack is class END PROCEDURE Stack::Add(AStackableItem) // This ones redundant :InStack::Add(AStackableItem) PROCEDURE Stack::AddTwo(Item1, Item2) // But this ones not :InStack::Add(Item2) // protected by forcing the call to InStack's Add routine, not a derived routing :InStack::Add(Item1) // Ditto
B. Avoid Derivations for Container Classes
The previous "Stack" examples used a "StackableItem" for keeping stacking. This means that stackable objects must be derived from StackableItem. Any class that keeps references to other classes should not do so in the form of derived classes but should keep such objects as foreign. Stack then becomes:
StackableItem is Class Next is StackableItem Storage is object END
InStack is class TopofStack is StackableItem END PROCEDURE InStack::Add(Item) Top is StackableItem dynamic = new StackableItem Top:Storage = Item Top:Next = :TopOfStack :TopOfStack = Top
Stack is class END PROCEDURE Stack::Add(AStackableItem) // This ones redundant :InStack::Add(AStackableItem)
Finally, when defining libraries, it would make sense to use a helpful yet unique naming convention to preclude the unexpected. For example, it is highly unlikely someone would override stkADD for stack ADD. |