Product Site

4.1.4. Metaclasses

A metaclass is a class you can use to create another class. The Class class is the metaclass of all the classes Rexx provides. This means that instances of .Class are themselves classes. The Class class is like a factory for producing the factories that produce objects.

To change the behavior of an object that is an instance, you generally use subclassing. For example, you can create Statarray, a subclass of the Array class. The statArray class can include a method for computing a total of all the numeric elements of an array.
Example 4.1. Creating an array subclass
/* Creating an array subclass for statistics */

::class statArray subclass array public

::method init    /*  Initialize running total and forward to superclass */
  expose total
  total = 0
  forward class (super)

::method put     /*  Modify to increment running total */
  expose total
  use arg value
  total = total + value  /* Should verify that value is numeric!!! */
  forward class (super)

::method "[]="   /*  Modify to increment running total */
  forward message "PUT"

::method remove  /*  Modify to decrement running total */
  expose total
  use arg index
  forward message "AT" continue
  total = total - result
  forward class (super)

::method average /*  Return the average of the array elements */
  expose total
  return total / self~items

::method total   /*  Return the running total of the array elements */
  expose total
  return total

You can use this method on the individual array instances, so it is an instance method.

However, if you want to change the behavior of the factory producing the arrays, you need a new class method. One way to do this is to use the ::METHOD directive with the CLASS option. Another way to add a class method is to create a new metaclass that changes the behavior of the Statarray class. A new metaclass is a subclass of .class.

You can use a metaclass by specifying it in a SUBCLASS or MIXINCLASS message or on a ::CLASS directive with the METACLASS option.

If you are adding a highly specialized class method useful only for a particular class, use the ::METHOD directive with the CLASS option. However, if you are adding a class method that would be useful for many classes, such as an instance counter that counts how many instances a class creates, you use a metaclass.

The following examples add a class method that keeps a running total of instances created. The first version uses the ::METHOD directive with the CLASS option. The second version uses a metaclass.

Version 1
Example 4.2. Adding a CLASS method
/* Adding a class method using ::METHOD */

a = .point~new(1,1)               /* Create some point instances  */
say "Created point instance" a
b = .point~new(2,2)               /* create another point instance */
say "Created point instance" b
c = .point~new(3,3)               /* create another point instance */
say "Created point instance" c
                                  /* ask the point class how many */
                                  /* instances it has created     */
say "The point class has created" .point~instances "instances."



::class point public                /* create Point class           */

::method init class
  expose instanceCount
  instanceCount = 0                 /* Initialize instanceCount     */
  forward class (super)             /* Forward INIT to superclass   */

::method new class
  expose instanceCount              /* Creating a new instance      */
  instanceCount = instanceCount + 1 /* Bump the count               */
  forward class (super)             /* Forward NEW to superclass    */

::method instances class
  expose instanceCount              /* Return the instance count    */
  return instanceCount


::method init
  expose xVal yVal                  /* Set object variables         */
  use arg xVal, yVal                /* as passed on NEW             */

::method string
  expose xVal yVal                  /* Use object variables         */
  return "("xVal","yVal")"          /* to return string value       */

Version 2
/* Adding a class method using a metaclass  */

a = .point~new(1,1)                    /* Create some point instances  */
say "Created point instance" a
b = .point~new(2,2)
say "Created point instance" b
c = .point~new(3,3)
say "Created point instance" c
                                       /* ask the point class how many */
                                       /* instances it has created     */
say "The point class has created" .point~instances "instances."

::class InstanceCounter subclass class /* Create a new metaclass that */
                                       /* will count its instances     */
::method init
  expose instanceCount
  instanceCount = 0                    /* Initialize instanceCount     */
  forward class (super)                /* Forward INIT to superclass   */

::method new
  expose instanceCount                 /* Creating a new instance      */
  instanceCount = instanceCount + 1    /* Bump the count               */
  forward class (super)                /* Forward NEW to superclass    */

::method instances
expose instanceCount                   /* Return the instance count    */
return instanceCount


::class point public metaclass InstanceCounter  /* Create Point class */
                                       /* using InstanceCounter metaclass */
::method init
  expose xVal yVal                     /* Set object variables         */
  use arg xVal, yVal                   /* as passed on NEW             */

::method string
  expose xVal yVal                     /* Use object variables         */
  return "("xVal","yVal")"             /* to return string value       */