Creating and Using Classes and Methods

You can define a class using either directives or messages.

To define a class using directives, you place a ::CLASS directive at the end of your source program:

::class "Account"

This creates an Account class that is a subclass of the Object class. (See The Object Class for a description of the Object class.) The string "Account" is a string identifier for the new class.

Now you can use ::METHOD directives to add methods to your new class. The ::METHOD directives must immediately follow the ::CLASS directive that creates the class.

::method type
  return "an account"

::method "name="
  expose name
  use arg name

::method name
  expose name
  return name

This adds the methods TYPE, NAME, and NAME= to the Account class.

You can create a subclass of the Account class and define a method for it:

::class "Savings" subclass account
::method type
return "a savings account"

Now you can create an instance of the Savings class with the NEW method (see NEW) and send TYPE, NAME, and NAME= messages to that instance:

asav = .savings~new
say asav~type
asav~name = "John Smith"

The Account class methods NAME and NAME= create a pair of access methods to the account object variable NAME. The following directive sequence creates the NAME and NAME= methods:

::method "name="
  expose name
  use arg name

::method name
  expose name
  return name

You can replace this with a single ::METHOD directive with the ATTRIBUTE option. For example, the directive

::method name attribute

adds two methods, NAME and NAME= to a class. These methods perform the same function as the NAME and NAME= methods in the original example. The NAME method returns the current value of the object variable NAME; the NAME= method assigns a new value to the object variable NAME.

Using Classes

When you create a new class, it is always a subclass of an existing class. You can create new classes with the ::CLASS directive or by sending the SUBCLASS or MIXINCLASS message to an existing class. If you specify neither the SUBCLASS nor the MIXINCLASS option on the ::CLASS directive, the superclass for the new class is the Object class, and it is not a mixin class.

Example of creating a new class using a message:

persistence = .object~mixinclass("Persistence")
myarray=.array~subclass("myarray")~~inherit(persistence)

Example of creating a new class using the directive:

::class persistence mixinclass object
::class myarray subclass array inherit persistence

Scope

A scope is the methods and object variables defined in a single class. Only methods defined in a particular scope can access object variables within that scope. This means that object variables in a subclass can have the same names as object variables in a superclass, because the object variables are at different scopes.

Defining Instance Methods with SETMETHOD or ENHANCED

In Rexx, methods are usually associated with instances using classes, but it is also possible to add methods directly to an instance using the SETMETHOD (see SETMETHOD) or ENHANCED (see ENHANCED) method.

All subclasses of the Object class inherit SETMETHOD. You can use SETMETHOD to create one-off objects, objects that must be absolutely unique so that a class that is capable of creating other instances is not necessary. The Class class also provides an ENHANCED method that lets you create new instances of a class with additional methods. The methods and the object variables defined on an object with SETMETHOD or ENHANCED form a separate scope, like the scopes the class hierarchy defines.

Method Names

A method name can be any string. When an object receives a message, the language processor searches for a method whose name matches the message name in uppercase.

Note: The language processor also translates the specified name of all methods added to objects into uppercase characters.

You must surround a method name with quotation marks when it contains characters that are not allowed in a symbol (for example, the operator characters). The following example creates a new class (the Cost class), defines a new method (%), creates an instance of the Cost class (mycost), and sends a % message to mycost:

cost=.object~subclass("A cost")
cost~define("%", 'expose p; say "Enter a price."; pull p; say p*1.07;')
mycost=cost~new
mycost~"%"        /* Produces:  Enter a price.             */
                  /* If the user specifies a price of 100, */
                  /* produces: 107.00                      */
		  

Default Search Order for Method Selection

The search order for a method name matching the message is for:

  1. A method the object itself defines with SETMETHOD or ENHANCED. (See SETMETHOD .)

  2. A method the object's class defines. (Note that an object acquires the instance methods of the class to which it belongs at the time of its creation. If a class gains additional methods, objects created before the definition of these methods do not acquire these methods.)

  3. A method that a superclass of the object's class defines. This is also limited to methods that were available when the object was created. The order of the INHERIT (see INHERIT) messages sent to an object's class determines the search order of the superclass method definitions.

This search order places methods of a class before methods of its superclasses so that a class can supplement or override inherited methods.

If the language processor does not find a match for the message name, the language processor checks the object for a method name UNKNOWN. If it exists, the language processor calls the UNKNOWN method and returns as the message result any result the UNKNOWN method returns. The UNKNOWN method arguments are the original message name and a Rexx array containing the original message arguments.

If the object does not have an UNKNOWN method, the language processor raises a NOMETHOD condition.

Defining an UNKNOWN Method

When an object that receives a message does not have a matching message name, the language processor checks if the object has a method named UNKNOWN. If the object has an UNKNOWN method, the language processor calls UNKNOWN, passing two arguments. The first argument is the name of the method that was not located. The second argument is an array containing the arguments passed with the original message.

If you define an UNKNOWN method, you can use the following syntax:

>>-UNKNOWN(messagename,messageargs)----------------------------><

Changing the Search Order for Methods

You can change the usual search order for methods by:

  1. Ensuring that the receiver object is the sender object. (You usually do this by specifying the special variable SELF--see SELF.)

  2. Specifying a colon and a class symbol after the message name. The class symbol can be a variable name or an environment symbol. It identifies the class object to be used as the starting point for the method search.

    The class object must be a superclass of the class defining the active method, or, if you used SETMETHOD to define the active method, the object's own class. The class symbol is usually the special variable SUPER (see SUPER) but it can be any environment symbol or variable name whose value is a valid class.

Suppose you create an Account class that is a subclass of the Object class, define a TYPE method for the Account class, and create the Savings class that is a subclass of Account. You could define a TYPE method for the Savings class as follows:

savings~define("TYPE", 'return "a savings account"')

You could change the search order by using the following line:

savings~define("TYPE", 'return self~type:super "(savings)"')

This changes the search order so that the language processor searches for the TYPE method first in the Account superclass (rather than in the Savings subclass). When you create an instance of the Savings class (asav) and send a TYPE message to asav:

say asav~type

an account (savings) is displayed. The TYPE method of the Savings class calls the TYPE method of the Account class, and adds the string (savings) to the results.

Public and Private Methods

A method can be public or private. Any object can send a message that runs a public method. A private method runs only when an object sends a message to itself (that is, using the variable SELF as the message receiver). Private methods include methods at different scopes within the same object. (Superclasses can make private methods available to their subclasses while hiding those methods from other objects.) A private method is like an internal subroutine. It provides common functions to the object methods but is hidden from other programs.

Initialization

Any object requiring initialization at creation time must define an INIT method. If this method is defined, the class object runs the INIT method after the object is created. If an object has more than one INIT method (for example, it is defined in several classes), each INIT method must forward the INIT message up the hierarchy to complete the object's initialization.

Example:

asav = .savings~new(1000.00, 6.25)
say asav~type
asav~name = "John Smith"

::class Account

::method INIT
  expose balance
  use arg balance

::method TYPE
  return "an account"

::method name attribute

::class Savings subclass Account

::method INIT
  expose interest_rate
  use arg balance, interest_rate
  self~init:super(balance)

::method type
  return "a savings account"

The NEW method of the Savings class object creates a new Savings object and calls the INIT method of the new object. The INIT method arguments are the arguments specified on the NEW method. In the Savings INIT method, the line:

self~init:super(balance)

calls the INIT method of the Account class, using just the balance argument specified on the NEW message.

Object Destruction and Uninitialization

Object destruction is implicit. When an object is no longer in use, Rexx automatically reclaims its storage. If the object has allocated other system resources, you must release them at this time. (Rexx cannot release these resources, because it is unaware that the object has allocated them.)

Similarly, other uninitialization processing may be needed, for example, by a message object holding an unreported error. An object requiring uninitialization should define an UNINIT method. If this method is defined, Rexx runs it before reclaiming the object's storage. If an object has more than one UNINIT method (defined in several classes), each UNINIT method is responsible for sending the UNINIT method up the object hierarchy.

Required String Values

Rexx requires a string value in a number of contexts within instructions and built-in function calls.

If you supply an object other than a string in these contexts, by default the language processor converts it to some string representation and uses this. However, the programmer can cause the language processor to raise the NOSTRING condition when the supplied object does not have an equivalent string value.

To obtain a string value, the language processor sends a REQUEST("STRING") message to the object. Strings and other objects that have string values return the appropriate string value for Rexx to use. (This happens automatically for strings and for subclasses of the String class because they inherit a suitable MAKESTRING method from the String class.) For this mechanism to work correctly, you must provide a MAKESTRING method for any other objects with string values.

For other objects without string values (that is, without a MAKESTRING method), the action taken depends on the setting of the NOSTRING condition trap. If the NOSTRING condition is being trapped (see Conditions and Condition Traps), the language processor raises the NOSTRING condition. If the NOSTRING condition is not being trapped, the language processor sends a STRING message to the object to obtain its readable string representation (see the STRING method of the Object class STRING) and uses this string.

When comparing a string object with the .nil object, if the NOSTRING condition is being trapped, then

if string = .nil

will raise the NOSTRING condition, whereas

if .nil = string

will not as the .nil objects "=" method does not expect a string as an argument.

Example:

d = .directory~new
say substr(d,5,7)         /* Produces "rectory" from "a Directory" */
signal on nostring
say substr(d,5,7)         /* Raises the NOSTRING condition */
say substr(d~string,3,6)  /* Displays "Direct" */

For arguments to Rexx object methods, different rules apply. When a method expects a string as an argument, the argument object is sent the REQUEST("STRING") message. If REQUEST returns the NIL object, then the method raises an error.

Concurrency

Rexx supports concurrency, multiple methods running simultaneously on a single object. See Concurrency for a full description of concurrency.