Enhancement methods: Difference between revisions

From m204wiki
Jump to navigation Jump to search
 
(3 intermediate revisions by 3 users not shown)
Line 196: Line 196:


===Invoking a Local Alias of an enhancement method===
===Invoking a Local Alias of an enhancement method===
You can also define a [[Local and Common entities#Local aliases|local alias]] of an enhancement method, and invoke the enhancement method using the alias:
You can also define a <var>[[Local and Common entities#Local aliases|Local Alias]]</var> of an enhancement method, and invoke the enhancement method using the alias:


<p class="code">local alias %(stringlist):everyOther for (+utility)everyOther
<p class="code">local alias (stringlist):everyOther for (+utility)everyOther
  ...
  ...
%mylist:(+utility)everyOther:print
%mylist:everyOther:print
</p>
</p>
This results in a much more natural calling syntax, as though the user-written enhancement method were an integral component of the enhanced class.


==Enhancement methods and inheritance==
==Enhancement methods and inheritance==

Latest revision as of 23:31, 25 July 2017

Background

In broad terms, a class provides descriptions of two things:

  1. 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.
  2. 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:

  1. Add a new method to the class.
  2. Create an extension of the class that contains the new method.
  3. 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.
arguments 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.

Invoking a Local Alias of an enhancement method

You can also define a Local Alias of an enhancement method, and invoke the enhancement method using the alias:

local alias (stringlist):everyOther for (+utility)everyOther ... %mylist:everyOther:print

This results in a much more natural calling syntax, as though the user-written enhancement method were an integral component of the enhanced class.

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.

See also