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 User Language programs, but it is stored more or less identically to User Language 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 Sirius Mods version 7.2, 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 User Language.
- 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
Sirius Mods version 7.2 introduced a new type of shared method called an
enhancement method to provide 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%this
.
As with unshared methods, the 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
In addition to Enhancement methods, Sirius Mods 7.2 introduced
Intrinsic methods and classes.
And, just as you can create enhancement methods for other system
classes, you can create enhancement methods for 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
%this%hyp
to 5.
As can be seen in this example, for a Float enhancement method, the
implicitly defined 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.