Dynamic dispatch

From m204wiki
Revision as of 22:16, 14 October 2013 by JAL (talk | contribs) (→‎See also)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

While inheritance and polymorphism can be useful in and of themselves, they are relatively limited and incapable of dealing with truly complex relationships between base and extension classes. More specifically, they provide no way of having extension-class specific processing performed when needed.

For example, suppose there is an Animal class (used by a zoo) with many extensions (for each species of animal). There might be code somewhere to manage the feeding of the animals. But because of the different eating patterns of the various animals, there's probably no general (that is, non-family or non-species specific) way to determine the next feeding time for a particular animal. So how best to do this?

The answer lies in dynamic dispatch, as described in the following sub-sections.

Overridable versus Abstract methods

You can declare a function in the Animal class as overridable:

class animal public ... function nextFeeding is float overridable ... end public ...

The actual implementation of this overridable method then provides a default NextFeeding method for all Animal class objects. For example, the following implementation makes the rather silly assumption that most animals need to be fed every eight hours (with seconds as the time units):

function nextFeeding is float overridable return %this:lastFeeding + 8 * 60 * 60 end function

Now, any class that extends the Animal class can provide an alternative NextFeeding function that overrides, or implements, the base class function:

class bat extends animal public ... function nextFeeding is float - implements nextFeeding in animal ... end public function nextFeeding is float - implements nextFeeding in animal ... bat specific next feeding calculations end function ...

But how does one invoke the appropriate NextFeeding method for a particular object? The answer is, by simply invoking the NextFeeding method in the Animal class:

%creature is object animal ... print %creature:nextFeeding

When the Nextfeeding method is invoked in the above example, SOUL determines the true class (remember, %creature can reference something of the Animal class, or of any extension class of Animal) of the object referenced by %creature, and it calls the appropriate NextFeeding method for that object.

Note that this actually achieves a narrowing assignment (assignment of a base class variable to an extension class variable), because the object reference from the base class variable is assigned to the %this variable, which is of the extension class, in the extension class method. This is a very structured and specialized narrowing assignment that avoids many of the problems and pitfalls generally associated with narrowing assignments.

For example, in the following code, the Bat class NextFeeding method will be called, because %creature will reference a Bat object:

%creature is object animal ... %creature = %(bat):new print %creature:nextFeeding

Any extension class of the Animal class that doesn't implement the NextFeeding method will simply have the Animal class's NextFeeding method run on its behalf.

Suppose there were no sensible default behavior for a method (as is the case, really, for the Nextfeeding function). Suppose it was expected that extension classes would always provide a class-specific implementation of such a method. Then, instead of declaring the method Overridable, the base class could declare the method as Abstract:

public ... function nextFeeding is float abstract ... end public

The term Abstract here means that the containing class provides no implementation of the indicated method, but instead, it expects an extension class to do so. Now, if a base class has at least one Abstract method declared, what if the method is invoked on an object of the base class only, that is, not for an extension class? The answer is that there is no sensible thing to do, so you are not allowed to create an instance of an object of the base class only.

Declaring an Abstract method for a class makes the class itself an abstract class. This must be indicated on at least the first declaration for the class, by specifying the Abstract keyword following the class name:

class animal abstract ... public ... function nextFeeding is float abstract ... end public ...

If a class is abstract, you can declare variables of that class:

%sickAnimal is object animal

But you cannot create an object instance of that class. That is, if class Animal is abstract, both of the following two New invocations are invalid:

%sickAnimal is object animal ... %sickAnimal = new ... %sickAnimal = %(animal):new

However, if a class extends the base class and implements all its abstract methods, an object instance of that class can be assigned to an abstract class variable. For example, if the Bat class implemented all the Animal class abstract methods, the following would be perfectly valid:

%sickAnimal is object animal ... %sickAnimal = %(bat):new

Note that it is possible to declare a class as Abstract even if it has no Abstract methods. You might do this to leave open the possibility of adding abstract methods to the class in the future. Or you might do this if, for whatever reason, it does not make sense to have an instance of a base class — the class only makes sense when extended.

An abstract class can be extended by another abstract class, and an abstract method can be implemented by another abstract method:

class nocturnalAnimal extends animal public ... function nextFeeding is float - implements nextFeeding in animal abstract ... end public ...

In the case of the preceding example, an extension class of the nocturnalAnimal extension presumably implements the Nextfeeding method.

It is even possible to have an abstract class extend a concrete (non-abstract) class, and to have an abstract method implement an overridable method.

If a concrete class extends an abstract class with abstract methods, all abstract methods in the base class must be implemented in the concrete extension class.

Sirius Mods 7.2 introduced Protected variable declarations. Protected variables are class variables that can be examined anywhere, but can only be updated by the containing class or by extension classes of that class. Protected variables can be thought of as providing an overridable variable facility and can be considered an alternative to ReadOnly Overridable properties. For more information about Protected variables see "Variable declarations".

Method definition rules

The following rules pertain to the definitions of overridable, abstract, or implementing methods:

  • Overridable or abstract methods can be any of the SOUL method types: Functions, Subroutines, or Properties.
  • Overridable or abstract methods can also have any type of parameter allowed in any other methods: Required, Optional, NameAllowed, NameRequired, AllowNull, etc.
  • Methods that implement base class methods must have identical parameter signatures to the base class methods. That is, all parameter names, parameter datatypes, optional, required, NameAllowed, NameRequired, and other parameter attributes must be the same in the abstract or overridable methods and in the methods that implement them.
  • Methods that implement base class properties and functions must have the same datatype for for their return or set values as the base class methods.
  • Methods that implement base class methods do not have to have the same method name as the base class method name. This is valid:

    function nightfeeding implements nextFeeding in animal

  • Methods that implement abstract or overridable base class methods can be in the Public or the Private block of the extension class:
    • If in the Public block, the method can be invoked directly by users of the extension class without going through dynamic dispatch.
    • If in the Private block, the code outside the class can only invoke the method via dynamic dispatch via the base class method.
  • An overridable or abstract method may be inside the private block of the base class:

    class animal private ... function nextFeeding is float abstract ... end private ...

    In such a case, the dynamic dispatch calls may only be made from inside the base class. But extension classes may implement the method, or if the method is abstract, as in this case, they must implement the method.

Super members

Sometimes, you may want a method that implements an overridable method to augment rather than replace the overridden method's processing, that is, to do some extra processing before or after the overridden method. An apparent way to accomplish this is to simply invoke the overridden method from inside the implementing method:

function nextFeeding is float implements nextFeeding in animal ... stuff before base method processing %foo = %this:(animal)nextFeeding ... stuff after base method processing end function

Unfortunately, the above syntax would simply result in the recursive calling of the extension class's implementing method — %this references an object of the extension class, so via dynamic dispatch, the extension class's NextFeeding method would be called. To get around this, a special member name, Super, is provided.

The Super member exists only in methods that implement an overridable method, and it refers to the overridden method without dynamic dispatch. To call an overridden method from an implementing method, you use the Super member like this:

function nextFeeding is float implements nextFeeding in animal ... stuff before base method processing %foo = %this:super ... stuff after base method processing end function

As usual, the this: above can be left off, so %this:super can be written simply as %super. If the method had parameters, they could be included after the Super, as if the Super was the overridden method name.

The Super designation makes no sense for methods that implement abstract methods, since with abstract methods, there is no base class method for the Super member to reference.

SOUL versus other languages

For programmers familiar with other object-oriented programming languages, it is worth pointing out that most of the facilities provided for dynamic dispatch by SOUL are no different than those provided by those other languages. However, some of the defaults in some other languages are different. For example:

  • In some languages, all methods are assumed to be overridable unless some keyword (usually Final) is specified after their declarations.
  • In some languages, a method in an extension class with the same name as an overridable method in a base class is automatically considered to implement the base class method. In SOUL, the Implements clause is required in a method declaration for a method to be considered to implement a base class method.

These differences are present in SOUL to facilitate its support for multiple inheritance, and also, independent of multiple inheritance, to improve the maintainability of SOUL code.

A final point: in many programming languages, the use of dynamic dispatch has a severe performance penalty. This is not the case for SOUL. While there is an exceedingly small performance penalty for using dynamic dispatch, this penalty is so small as to be unmeasurable in most cases. Performance cost should not be a consideration when trying to determine whether dynamic dispatch should be used.

See also