Enhancement methods: Difference between revisions
m (Created page with "==Background== In broad terms, a class provides descriptions of two things: <ol> <li>The data that describes the state of objects of that class. This state data is in the non-sha...") |
|||
(20 intermediate revisions by 5 users not shown) | |||
Line 6: | Line 6: | ||
[[Classes and Objects#Declaration blocks|public and private]] | [[Classes and Objects#Declaration blocks|public and private]] | ||
variables in the class. | variables in the class. | ||
For system classes, this data is hidden from <var class="product"> | For system classes, this data is hidden from <var class="product">SOUL</var> programs, but it is | ||
stored more or less identically to <var class="product"> | stored more or less identically to <var class="product">SOUL</var> variables. | ||
<li>The operations that can be performed on objects of that class. | <li>The operations that can be performed on objects of that class. | ||
These operations are the methods | These operations are the methods | ||
Line 18: | Line 18: | ||
on objects of the class that are ''not'' one of the standard methods | on objects of the class that are ''not'' one of the standard methods | ||
provided by the class. | provided by the class. | ||
Before <var class="product">Sirius Mods</var> version 7.2, there were three ways of addressing this: | <!-- Before <var class="product">Sirius Mods</var> version 7.2 --> | ||
Before the introduction of enhancement methods, there were three ways of addressing this: | |||
<ol> | <ol> | ||
<li>Add a new method to the class. | <li>Add a new method to the class. | ||
Line 29: | Line 30: | ||
<ul> | <ul> | ||
<li>If the class is a system class, new methods cannot be added | <li>If the class is a system class, new methods cannot be added | ||
using <var class="product"> | using <var class="product">SOUL</var>. | ||
<li>If the class belongs to a different group in the organization, | <li>If the class belongs to a different group in the organization, | ||
it can be problematic getting a new method added. | it can be problematic getting a new method added. | ||
<li>The method being added might need access to private variables | <li>The method being added might need access to private variables | ||
in another class, so the method must be added to the other class, not | in another class, so the method must be added to the other class, not | ||
Line 39: | Line 42: | ||
Option 2 is impractical in many cases: | Option 2 is impractical in many cases: | ||
<ul> | <ul> | ||
<li>Many system classes, such as [[ | <li>Many system classes, such as [[Collections|collections]], | ||
[[Intrinsic classes|intrinsic classes]], | [[Intrinsic classes|intrinsic classes]], | ||
and XmlNodes are not allowed to be extended. | and XmlNodes are not allowed to be extended. | ||
<li>It requires changing variable declarations to use the new class. | <li>It requires changing variable declarations to use the new class. | ||
If the variables, themselves, are parameters, callers might have to be | If the variables, themselves, are parameters, callers might have to be | ||
Line 47: | Line 51: | ||
This can produce a cascade effect where large chunks of code have to | This can produce a cascade effect where large chunks of code have to | ||
be changed to add an extra method to a class. | be changed to add an extra method to a class. | ||
<li>While inheritance is implemented quite efficiently, there '''is''' | <li>While inheritance is implemented quite efficiently, there '''is''' | ||
a cost in code complexity to using inheritance, and using inheritance | a cost in code complexity to using inheritance, and using inheritance | ||
Line 56: | Line 61: | ||
operates on a <var>Stringlist</var> and returns a new <var>Stringlist</var> that contains every | operates on a <var>Stringlist</var> and returns a new <var>Stringlist</var> that contains every | ||
other item in the input <var>Stringlist</var>. | other item in the input <var>Stringlist</var>. | ||
To accomplish this, you might create a shared method in a class called < | To accomplish this, you might create a shared method in a class called <code>Utility</code>: | ||
<p class="code">class utility | <p class="code">class utility | ||
public shared | public shared | ||
Line 82: | Line 87: | ||
While there is nothing wrong with this, per se, it blunts some of | While there is nothing wrong with this, per se, it blunts some of | ||
the benefits of object-oriented syntax. | the benefits of object-oriented syntax. | ||
Specifically, while the method is really operating on the < | Specifically, while the method is really operating on the <code>%mylist</code> object, | ||
the < | the <code>%mylist</code> object appears as a method parameter, rather than the method | ||
object. | object. | ||
In addition to obscuring the meaning of the statement, it means that the | In addition to obscuring the meaning of the statement, it means that the | ||
statement must be read "inside out". | statement must be read "inside out". | ||
That is, if the input to < | That is, if the input to <code>EveryOther</code> was, itself, the result of the method | ||
invocation, this method invocation would be inside the parentheses, after | invocation, this method invocation would be inside the parentheses, after | ||
< | <code>EveryOther</code>, even though it would actually be invoked before. | ||
For example, if the input to < | For example, if the input to <code>EveryOther</code> was the result of a <var>Sort</var>, the | ||
statement might look like: | statement might look like: | ||
<p class="code">%(utility):everyOther(%mylist:sortNew('1,20,A')):print | <p class="code">%(utility):everyOther(%mylist:sortNew('1,20,A')):print | ||
</p> | </p> | ||
<var class="product">Sirius Mods</var> version 7.2 introduced | <!-- <var class="product">Sirius Mods</var> version 7.2 introduced --> | ||
'''enhancement method''' | 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. | method on an object of another class. | ||
Because enhancement methods do not operate on objects of the containing | Because enhancement methods do not operate on objects of the containing | ||
class, they are considered shared methods of that class, so they are declared | class, they are considered shared methods of that class, so they are declared | ||
inside the <var>Public Shared</var> or <var>Private Shared</var> blocks of the class. | inside the <var>Public Shared</var> or <var>Private Shared</var> blocks of the class. | ||
==Enhancement method declaration syntax== | ==Enhancement method declaration syntax== | ||
An enhancement method declaration has the following syntax: | An enhancement method declaration has the following syntax: | ||
Line 110: | Line 116: | ||
<table class="syntaxTable"> | <table class="syntaxTable"> | ||
<tr><th>method | <tr><th>method | ||
</th><td>The method type — <var>Subroutine</var>, <var>Function</var>, or <var>Property</var>. An enhancement method cannot be a <var>[[ | </th><td>The method type — <var>Subroutine</var>, <var>Function</var>, or <var>Property</var>. An enhancement method cannot be a <var>[[Object oriented programming in SOUL#constructor|Constructor]]</var>. | ||
</td></tr> | </td></tr> | ||
<tr><th>class | <tr><th>class | ||
Line 131: | Line 137: | ||
</th><td>An object variable of the class against which the enhancement method operates. | </th><td>An object variable of the class against which the enhancement method operates. | ||
</td></tr> | </td></tr> | ||
<tr><th>+containerClass | <tr><th>+containerClass | ||
</th><td>The name of the class that contains the enhancement method definition. Note that the class name must be preceded by a plus sign (<tt>+</tt>) 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. | </th><td>The name of the class that contains the enhancement method definition. Note that the class name must be preceded by a plus sign (<tt>+</tt>) 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. | ||
</td></tr> | </td></tr> | ||
<tr><th>name | <tr><th>name | ||
</th><td>The name of the enhancement method. | </th><td>The name of the enhancement method. | ||
</td></tr> | </td></tr> | ||
<tr><th> | |||
<tr><th>arguments | |||
</th><td>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. | </th><td>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. | ||
</td></tr></table> | </td></tr></table> | ||
For example, to declare and define an enhancement method version of | For example, to declare and define an enhancement method version of | ||
the < | the <code>EveryOther</code> method described earlier, one would do something like | ||
the following: | the following: | ||
<p class="code">class utility | <p class="code">class utility | ||
Line 164: | Line 173: | ||
As can be seen in this example, an enhancement method has an | As can be seen in this example, an enhancement method has an | ||
implicitly declared object variable called <code>%this</code>. | implicitly declared object variable called <code>%this</code>. | ||
As with unshared methods, the < | As with unshared methods, the <code>%this</code> variable is a reference | ||
to the method object. | to the method object. | ||
Unlike unshared methods, the < | Unlike unshared methods, the <code>%this</code> variable is not an object of | ||
the containing class, but is, instead, an instance of the class | the containing class, but is, instead, an instance of the class | ||
to which the enhancement method applies. | to which the enhancement method applies. | ||
Line 185: | Line 194: | ||
This chain of methods can be read from left to right | This chain of methods can be read from left to right | ||
rather than from the inside out. | rather than from the inside out. | ||
===Invoking a Local Alias of an enhancement method=== | |||
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 | |||
... | |||
%mylist:everyOther:print | |||
</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== | ||
Enhancement methods are never automatically inherited by extension | Enhancement methods are never automatically inherited by extension | ||
Line 190: | Line 210: | ||
the <var>Inherit</var> keyword is specified on the class declaration for the | the <var>Inherit</var> keyword is specified on the class declaration for the | ||
extension class. | extension class. | ||
For example, the < | For example, the <code>EveryOther</code> method | ||
in the preceding section cannot be directly applied | in the preceding section cannot be directly applied | ||
to < | to <code>%mylist</code> if <code>%mylist</code> is actually an object of class <code>Funnylist</code>, | ||
where < | where <code>FunnyList</code> is an extension of class <var>Stringlist</var>. | ||
However, the method can be applied by explicitly specifying | However, the method can be applied by explicitly specifying | ||
the name of the class to which the method applies: | the name of the class to which the method applies: | ||
Line 212: | Line 232: | ||
</p> | </p> | ||
If this were specified, the < | If this were specified, the <code>EveryOther</code> enhancement method could | ||
be applied to objects of class < | be applied to objects of class <code>Funnylist</code> without any extra | ||
qualification. | qualification. | ||
< | <blockquote class="note">'''Note:''' | ||
Because an enhancement method is not defined | Because an enhancement method is not defined | ||
in the class to which the method applies, it cannot reference private | in the class to which the method applies, it cannot reference private | ||
variables in that class. | variables in that class. | ||
However, it '''can''' reference private members of instances | However, it '''can''' reference private members of instances | ||
of objects of the containing class. | of objects of the containing class. | ||
<p> | |||
In fact, one typical application for enhancement methods is to | In fact, one typical application for enhancement methods is to | ||
provide a method for creating a new instance of the containing | provide a method for creating a new instance of the containing | ||
Line 228: | Line 248: | ||
Such a method might not need to access private members of the | Such a method might not need to access private members of the | ||
method object class, but it might need to access private members of | method object class, but it might need to access private members of | ||
the containing class, so an enhancement factory method is | the containing class, so an enhancement factory method is perfectly suitable. </p> | ||
perfectly suitable. | </blockquote> | ||
==Intrinsic enhancement methods== | |||
Just as you can create enhancement methods for other system | |||
classes, you can create enhancement methods for the [[Intrinsic classes|intrinsic]] classes, | |||
classes, you can create enhancement methods for intrinsic classes, | |||
specifically for the <var>Float</var> and <var>String</var> classes. | specifically for the <var>Float</var> and <var>String</var> classes. | ||
Line 260: | Line 279: | ||
The preceding statement sets <code>%hyp</code> to 5. | The preceding statement sets <code>%hyp</code> to 5. | ||
As can be seen in this example, for a <var>Float</var> enhancement method, the | As can be seen in this example, for a <var>Float</var> enhancement method, the | ||
implicitly defined < | implicitly defined <code>%this</code> variable has a <var>Float</var> datatype. | ||
The following is an example of a <var>String</var> enhancement method that returns | The following is an example of a <var>String</var> enhancement method that returns | ||
Line 295: | Line 314: | ||
a need to create an enhancement method on a collection of that class. | a need to create an enhancement method on a collection of that class. | ||
For example, you might have an < | For example, you might have an <code>Order</code> class that describes an order | ||
for widgets. | for widgets. | ||
You might want to provide an enhancement method on < | You might want to provide an enhancement method on <code>Arraylist of | ||
Order<code> objects that creates a new <var>Arraylist</var> of objects that contains | Order</code> objects that creates a new <var>Arraylist</var> of objects that contains | ||
items that need to be back-ordered (there are insufficient widgets | items that need to be back-ordered (there are insufficient widgets | ||
in stock to satisfy the order). | in stock to satisfy the order). | ||
Line 350: | Line 369: | ||
would continue to be invoked. | would continue to be invoked. | ||
[[Category | ==See also== | ||
<ul> | |||
<li>[[Inheritance and polymorphism]] | |||
<li>[[Narrowing assignments and class tests]] | |||
<li>[[Dynamic dispatch]] | |||
<li>[[Object oriented programming in SOUL]] | |||
</ul> | |||
[[Category:Overviews]] | |||
[[Category:SOUL object-oriented programming topics]] |
Latest revision as of 23:31, 25 July 2017
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. |
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.