Enhancement methods
Background
In broad terms, a class provides descriptions of two things:
- The data that describes the state of objects of that class. This state data is in the non-shared public and private variables in the class. For system classes, this data is hidden from SOUL programs, but it is stored more or less identically to SOUL variables.
- The operations that can be performed on objects of that class. These operations are the methods (that is, the functions, subroutines, and properties) of the class. For system classes, the methods are in the Model 204 load module and are written in assembler.
When using a class, it is not uncommon to want to perform operations on objects of the class that are not one of the standard methods provided by the class. Before the introduction of enhancement methods, there were three ways of addressing this:
- Add a new method to the class.
- Create an extension of the class that contains the new method.
- Create a shared method in another class that takes objects of the first class as input parameters.
Option 1, above, is impractical in many cases:
- If the class is a system class, new methods cannot be added using SOUL.
- If the class belongs to a different group in the organization, it can be problematic getting a new method added.
- The method being added might need access to private variables in another class, so the method must be added to the other class, not the class to which the method applies.
Option 2 is impractical in many cases:
- Many system classes, such as collections, intrinsic classes, and XmlNodes are not allowed to be extended.
- It requires changing variable declarations to use the new class. If the variables, themselves, are parameters, callers might have to be changed to use the new class. This can produce a cascade effect where large chunks of code have to be changed to add an extra method to a class.
- While inheritance is implemented quite efficiently, there is a cost in code complexity to using inheritance, and using inheritance excessively can cause code to be very difficult to understand.
The most generally usable solution, then, is option 3, a shared method.
To illustrate the use of this option, suppose one needs a function that
operates on a Stringlist and returns a new Stringlist that contains every
other item in the input Stringlist.
To accomplish this, you might create a shared method in a class called Utility
:
class utility public shared function everyOther(%inlist is object stringlist) - is object stringlist end public shared function everyOther(%inlist is object stringlist) - is object stringlist %i is float %outlist is object stringlist %outlist = new for %i from 1 to %inlist:count by 2 %outlist:add(%inlist(%i)) end for return %outlist end function end class
Then, to invoke this method on, say, the Stringlist object %mylist
,
you might do something like the following:
%(utility):everyOther(%mylist):print
While there is nothing wrong with this, per se, it blunts some of
the benefits of object-oriented syntax.
Specifically, while the method is really operating on the %mylist
object,
the %mylist
object appears as a method parameter, rather than the method
object.
In addition to obscuring the meaning of the statement, it means that the
statement must be read "inside out".
That is, if the input to EveryOther
was, itself, the result of the method
invocation, this method invocation would be inside the parentheses, after
EveryOther
, even though it would actually be invoked before.
For example, if the input to EveryOther
was the result of a Sort, the
statement might look like:
%(utility):everyOther(%mylist:sortNew('1,20,A')):print
A new type of shared method called an enhancement method has provided a better way of invoking a method on an object of another class. Because enhancement methods do not operate on objects of the containing class, they are considered shared methods of that class, so they are declared inside the Public Shared or Private Shared blocks of the class.
Enhancement method declaration syntax
An enhancement method declaration has the following syntax:
method (class ):name [otherMethodDesc]
Where:
method | The method type — Subroutine, Function, or Property. An enhancement method cannot be a Constructor. |
---|---|
class | The class of the objects to which the method applies. It cannot be the containing class nor an extension class of the containing class. |
name | The name of the enhancement method. The name can be the same name as that of other methods in the class, shared or non-shared, as long as it is not the same name as an enhancement method on the same class. |
otherMethodDesc | Method parameters, method type (for Functions and Properties), and other method qualifiers (like AllowNullObject). Any descriptors available to other methods are available to enhancement methods. |
Enhancement method invocation syntax
An enhancement method invocation has the following syntax:
object:(+containerClass)name [(arguments)]
Where:
object | An object variable of the class against which the enhancement method operates. |
---|---|
+containerClass | The name of the class that contains the enhancement method definition. Note that the class name must be preceded by a plus sign (+) to indicate an enhancement method invocation. The plus sign distinguishes an enhancement method container class name from other uses of class names inside parentheses that might appear in the same context. |
name | The name of the enhancement method. |
parameters | Any arguments the enhancement method might take as input. Optional, default, and named parameters work exactly the same way with enhancement methods as with any other methods. |
For example, to declare and define an enhancement method version of
the EveryOther
method described earlier, one would do something like
the following:
class utility public shared function (stringlist):everyOther - is object stringlist end public shared function (stringlist):everyOther - is object stringlist %i is float %outList is object stringlist %outlist = new for %i from 1 to %this:count by 2 %outlist:add(%this(%i)) end for return %outlist end function end class
As can be seen in this example, an enhancement method has an
implicitly declared object variable called %this
.
As with unshared methods, the %this
variable is a reference
to the method object.
Unlike unshared methods, the %this
variable is not an object of
the containing class, but is, instead, an instance of the class
to which the enhancement method applies.
This enhancement method could then be invoked as follows:
%mylist:(+utility)everyOther:print
As this example illustrates, the syntax for invoking an enhancement method is more natural from an object-oriented perspective than invoking a shared method that does the same thing.
Using an enhancement method in a chain of methods makes this point even clearer:
%mylist:sortNew('1,20,A'):(+utility)everyOther:print
This chain of methods can be read from left to right rather than from the inside out.
Enhancement methods and inheritance
Enhancement methods are never automatically inherited by extension
classes of the methods to which they apply, regardless of whether
the Inherit keyword is specified on the class declaration for the
extension class.
For example, the EveryOther
method
in the preceding section cannot be directly applied
to %mylist
if %mylist
is actually an object of class Funnylist
,
where FunnyList
is an extension of class Stringlist.
However, the method can be applied by explicitly specifying
the name of the class to which the method applies:
%mylist:(stringlist)(+utility)everyOther:print
In some languages this is referred to as casting, that is, casting a variable as one of its base classes.
You can also explicitly indicate that an extension class is to inherit a base class's enhancement method with a special form of the Inherit statement in the Public or Private Shared blocks:
public shared function (stringlist):everyOther - is object stringlist inherit (funnylist):everyOther from stringlist end public shared
If this were specified, the EveryOther
enhancement method could
be applied to objects of class Funnylist
without any extra
qualification.
Note:
Because an enhancement method is not defined in the class to which the method applies, it cannot reference private variables in that class. However, it can reference private members of instances of objects of the containing class.
In fact, one typical application for enhancement methods is to provide a method for creating a new instance of the containing class from an instance of the method object class. These are a special kind of factory method. Such a method might not need to access private members of the method object class, but it might need to access private members of the containing class, so an enhancement factory method is perfectly suitable.
Intrinsic enhancement methods
Just as you can create enhancement methods for other system classes, you can create enhancement methods for the intrinsic classes, specifically for the Float and String classes.
For example, the following definition creates an enhancement method that calculates the length of the hypotenuse of a triangle given the length of one side as the method object and the other side as a parameter:
class calc public shared function (float):hypotenuse(%otherSide is float) - is float end public shared function (float):hypotenuse(%otherSide is float) - is float return ((%this * %this) + - (%otherSide * %otherSide)):squareRoot end function end class
You can invoke the above method as follows:
%hyp = 3:(+calc)hypotenuse(4)
The preceding statement sets %hyp
to 5.
As can be seen in this example, for a Float enhancement method, the
implicitly defined %this
variable has a Float datatype.
The following is an example of a String enhancement method that returns the number of vowels in a string:
class myString public shared function (string):vowels is float end public shared function (string):vowels is float %i is float %vowels is float for %i from 1 to %this:length if %this:substring(%i, 1): - positionIn('aeiouAEIOU') then %vowels = %vowels + 1 end if end for return %vowels end function end class
You can invoke the above method as follows:
%nvowels = 'Canberra':(+myString)vowels
The preceding statement sets %nvowels
to 3
.
For a String enhancement method, the implicitly defined %this
variable
has a Longstring datatype.
Enhancement methods for Collections
Enhancement methods can be added to any class, including Collection classes. Given this capability, it is quite common for a class to have a need to create an enhancement method on a collection of that class.
For example, you might have an Order
class that describes an order
for widgets.
You might want to provide an enhancement method on Arraylist of
Order
objects that creates a new Arraylist of objects that contains
items that need to be back-ordered (there are insufficient widgets
in stock to satisfy the order).
The method might look something like this:
class order public shared function (arraylist of object order):backorderlist - is collection arraylist of object order end public shared function (arraylist of object order):backorderlist - is collection arraylist of object order %i is float %warehouse is object warehouse global %newlist is collection arraylist of object order %newlist = new for %i from 1 to %this:count if %this(%i):number gt - %warehouse:instock(%this(%i):widgetId) %newlist:add(%this) end if end for return %newlist end function end class
To invoke this enhancement method, one might do something like:
%backOrders = %orders:(+order)backOrderlist
However, because a class has a special relationship to collections of objects of that class, it is not necessary to indicate the class of the enhancement method. That is, you can also write the above method invocation as:
%backOrders = %orders:backOrderlist
It is possible to create enhancement methods with the same name as standard collection methods. For example, it is possible to create an Add method. If you create such a method, the collection class method is completely hidden. That is, there is no way to invoke the collection class method of the same name, even from inside the enhancement method. For this reason, it is generally not a good idea to create an enhancement method for a collection class with the same name as a collection class method.
The ability to hide collection class methods is available mainly to preserve backward compatibility when a new collection class method is introduced. If there were already enhancement methods of the same name, those methods would continue to be invoked.