Classes and Objects
Classes
A class is a collection of attributes that describe an object and of operations that may be performed on an object. The attributes of an object are formally declared as variables, while the operations are declared as methods. In the Janus SOAP User Language Interface, there are four types of methods: subroutines, functions, properties, and constructors. While a class describes general attributes and methods, a specific instance of the class is called an object, and variables that refer to those instances are called object variables.
The description of a class, all variables, and all methods of the class must be contained inside one or more Class blocks:
begin ... class car ... end class ... class car ... end class car ... end
This is slightly different from most other object-oriented languages, which require an entire class definition to be within a single class block (though sometimes a keyword other than Class is used to mark the start of the class definition). The main reason for allowing multiple class blocks is that they let you selectively include methods in a class as they are needed in a request, rather than having to compile all the methods in a class if any one is needed.
For example, Subroutine Paint
for Class Car
could be kept in a procedure
as follows:
PROCEDURE CLASS.CAR.PAINT !dupexit class car subroutine paint(%color is string len 32) ... end subroutine end class END PROCEDURE
!dupexit
causes the procedure to be exited on all but the first Include, which ensures that the subroutine is only defined once.
Attempting to define it more than once would result in compilation errors, of course.
Given the above procedure, code that needs to use Subroutine Paint
in class Car
could simply Include procedure CLASS.CAR.PAINT
:
include class.car.paint ... %chevy is object car ... %chevy:paint('Teal')
Subroutine Paint
can only be defined after it has been declared inside a declaration block for class Car
. If a class is broken up in such a way that some methods are in their own procedures, it is likely that the class's declaration blocks would all be contained in another procedure, called something like CLASS.CAR
in this example. And that procedure would have to be Included'ed before the procedure that defines the Paint
method:
include class.car include class.car.paint ... %chevy is object car ... %chevy:paint('Teal')
Class block syntax
The full syntax of the Class block is:
[Local] Class className [[Abstract]] - [[Alias (Class and Structure)|[Alias=aliasList]]] - [Extends=extendList] - [[RequiredSuffix (Class and Structure)|[RequiredSuffix=suffix]]] [[#Declaration blocks|[declaration blocks]]] [[Methods#Method definition syntax|[method definitions]]] End Class [className]
Syntax terms
Local | Indicates that the class can only be referenced inside the containing scope. A class declared inside a method or complex subroutine must be Local. A class in the outermost (Begin/End) scope can be either local, in which case it cannot be referenced in other scopes such as methods or complex subroutines, or not local, in which case it can be referenced anywhere in the request. |
---|---|
Abstract | Indicates that the class is abstract, that is, no instance of the class can be created unless it is an instance of a non-abstract extension class. |
className | Consists of any valid alphanumeric and other non-separator characters. |
aliasList | A list of aliases for the class, with each alias separated by the word And. Class aliases are only available in Sirius Mods 7.8 and later. |
extendList | A list of classes that this class extends. |
suffix | A non-quoted string of characters that must end the name of any object variable being declared for the class. |
The (one or more) class blocks for a class contain the declaration blocks and method definitions for the class, which together comprise the class definition. The class definition consists of zero or more declaration blocks and zero or more method definitions. It is valid to define a class with no declarations and no methods, though such a class is not going to be particularly useful.
Multiple class blocks for a class are, in effect, combined to form one class definition. Class blocks after the first may exactly repeat any of the Alias, Extends, or RequiredSuffix clauses from the first block for a class, but they may not change these clauses.
Classes must be declared outside of any other block structures such as For loops, If blocks, or other Class blocks.
Once a class is declared, you use the Object keyword on variable declarations to declare objects as instances of a class:
%malibu is object car %outback is object car
Colons in class names
The colon character (:) is allowed in User Language class names. The following, for example, is a valid class declaration:
class customer:order
No colon-demarcated part of a class name can be the word System.
For example, System
, System:foo
, Foo:system
, and Foo:system:bar
are all invalid class names.
While there is no special meaning to the colon-demarcated parts of a class name, the intent is that the colons be used to group classes, and that higher-level qualifiers come before lower-level ones.
For example, in class name MiddleEarth:rohan:rider
, a Rider
class falls under a grouping of classes qualified by Rohan
.
This grouping, itself, falls under the larger grouping of MiddleEarth
.
Using the colon in this way is likely to make it easier to take advantage of future class management features that depend on a hierarchical, colon-based, class naming scheme.
Declaration blocks
Declaration blocks are another feature of the Janus SOAP User Language Interface that is unusual among object-oriented languages. Declaration blocks describe all the variables and methods that make up a class, but they contain no code.
Most object-oriented languages allow variable declarations of all types to be mixed arbitrarily with method definitions of all types. Though this appears to be very convenient, it can make it difficult for users or maintainers of a class to determine the general framework of the class. The Janus SOAP User Language Interface requires all variables and methods of a class to be declared in a few blocks, and in those blocks, to be organized in ways that are significant to their behavior.
The Janus SOAP User Language Interface allows no more than one of each of four declaration blocks:
- Public
- Private
- Public Shared
- Private Shared
The Public block contains the declaration of all class members (variables that are associated with an instance of the class, and methods that operate on an instance of the class) that are "public", that is available to users of the class. The Private block contains the declaration of all members that are "private", that is available only to methods inside a class definition.
The Shared blocks contain members that are shared among all instances of the class and so are able to be referenced independent of any instance of the class. Public Shared members are available to all users of the class, and Private Shared members are available only within the class definition. Shared variables and methods are discusssed in more detail in Shared class members.
The declaration blocks contain declarations for all variables and methods in a class, and they must appear before any method definitions.
Declaration block syntax
The format of the declarations blocks is:
{Public | Private} [Shared] [Allow clauses] [Disallow clauses] [member declarations] End {Public | Private} [Shared] [className]
Syntax terms
Public | Private | Indicates whether the block describes members that can be referenced outside the class (Public) or members that can only be referenced inside the class (Private). |
---|---|
End | Marks the end of the declaration block. It must be followed immediately by Public or Private which is then possibly followed by Shared. The keywords following End must exactly match the block type: for a Public block, End Public is required; for a Private Shared block, End Private Shared is required. The class name can, optionally, be indicated after the block type on the End clause. |
Allow clauses | Indicate special operations allowed by the class. It can only be specified in a Public (non-shared) block, and consist of the Allow keyword followed by Auto, Copy, DeepCopy, or Narrow.
There can be multiple Allow clauses in a declaration block. Allow clauses are optional and can be interspersed with Disallow clauses and member declarations. |
Disallow clauses | Indicate operations not allowed by the class. It can only be specified in a Public (non-shared) block, and consist of the Disallow keyword followed by Discard, or New.
There can be multiple Disallow clauses in a declaration block. Disallow clauses are optional and can be interspersed with Allow clauses and member declarations. |
member declarations | Declare variables or methods in the class. These are described in following subsections.
While member declarations are optional, they tend to appear in almost all declarations blocks and a class without member declarations in some declaration blocks would be pretty much useless. Member declaration clauses can be interspersed with Allow and Disallow clauses. |
Allow clause
As the name suggests, the Allow clause indicates things one is allowed to do with the object instances of the class that one ordinarily would be allowed to do:
Allow Auto | Indicates that an Auto New clause may be used on the declaration of variables of the type of the class being defined. If the class is an extension class, Allow Auto must also be specified on the class's base class(es). |
---|---|
Allow Copy or Allow DeepCopy | Makes available the system Copy or DeepCopy method, respectively, for objects in the class. |
Allow Narrow | Permits narrowing assignments for objects in the class. |
Allow Auto and constructors
Prior to version 7.6 of the Sirius Mods, a class defined with Allow Auto is not allowed to contain a constructor. As of version 7.6, Allow Auto is allowed in a class that has a constructor. However, for an object variable declared with Auto New in such a class, an automatic instantiation of the object does not call any of the constructors in the class, even if the class has defined an explicit constructor with the name New.
For example, the following request prints %y:b = 0
— the class's New
constructor does not get called by %y:b
:
b class y public allow auto variable b is float constructor new end public constructor new %this:b = 1234 end constructor end class %y is object y auto new printText {~} = {%y:b} end
Disallow clause
As the name suggests, the Disallow clause indicates things that one is not allowed to do with the object instances of the class that ordinarily would be allowed:
Disallow Discard | Indicates that objects of the class cannot be explicitly discarded outside the class. Objects of the class can still be explicitly Discarded inside the class. |
---|---|
Disallow New | Indicates that explicit construction of instances of the class using the New constructor is not allowed outside the class. Objects of the class can still be explicitly created with New inside the class, and other public constructors for the class can be invoked outside the class. |
Variable declarations
Variable declarations in a class declaration block must begin with Variable followed by the variable name (without a percent sign). This is followed by any standard User Language variable declaration, with the exception that:
- Static is not valid in a non-Shared block.
- Global is not valid in a non-Shared block and requires the global name in a Shared block.
- Common is not allowed.
- The keywords to indicate “publicity” (Private, Private Shared, Public, and Public Shared) are allowed. If specified, they must match the publicity of the enclosing declaration block.
- Auto followed by New or an enumeration value is allowed.
- ReadOnly and Protected are allowed in Sirius Mods 7.2 and later. Like standard Model 204 variable declarations, the keyword Is immediately after the variable name on variable declarations is optional.
An example follows of a class block with some variable declarations:
class car public variable price is fixed dp 2 variable make is string len 32 variable description is longstring end public end class
Class variables can be referenced via an instance of that class, that is,
via an object variable, like %gremlin
below:
%gremlin is object car ... printText The price is {%gremlin:price}
The colon (:
) that separates the object variable %gremlin
and the class member variable price
, above, may alternatively be
specified with a one or more blanks before and/or after it.
In Sirius Mods 7.2 and later, it is possible to to declare a variable as ReadOnly or Protected. A ReadOnly variable can be examined by code outside the class, but it can only be updated inside the class. A Protected variable can be examined by code outside the class, but it can only be updated inside the class or by code inside an extension class. Since a ReadOnly or Protected variable must be accessible outside the class, it would make no sense for such a variable to be in a Private section, so this is not allowed.
ReadOnly and Protected variables are useful for providing an efficient means of accessing relatively static information about objects in a class. Unlike a property, retrieving ReadOnly or Protected variables requires no code to be run in the class.
The term Protected is something of a misnomer. In a very real sense, Protected variables aren't protected, at all. After all, they can be updated by extension classes. Their real purpose is to act as a sort of overridable variable, that is, a value that can be overridden by an extension class.
For example, suppose a class has a ReadOnly variable called PartNumber
. Perhaps PartNumber
is set by the class's constructor, and then it is never set again (for a particular object instance).
Now, suppose this class wants to allow extension classes to set a different PartNumber
, probably in their constructors.
One approach would be to make PartNumber
an Overridable ReadOnly property.
But this is a somewhat heavyweight approach, as it requires a bit of code (the property Get method) for each extension class, and this code has to be run every time PartNumber is retrieved.
Instead, PartNumber
could be made a Protected variable, and an extension class's constructor could simply set it after calling the base class constructor.
This allows the extension classes to override the base class's PartNumber
using little extra code and using a very efficient access path for the value.
Protected variables differ from Overridable ReadOnly properties, however, in that an Overridable ReadOnly property always guarantees that the extension class's code is run, so it overrides the base class's properties. With a Protected variable, it would be possible for an extension class to set it, and for the base class to then set it to something else, undoing the extension class action.
Because they can be updated by both base and extension classes, Protected variables are probably most useful for very static values, like values that are only set by the constructors. Use of Protected variables that can be set throughout the life of objects of a class is likely to be error-prone, as it requires careful coordination of updates between the base and extension classes.
Method declarations and method types
In addition to variable declarations, a declaration block can also contain method declarations. There are four basic types of methods:
- Functions
- Methods that produce an output value.
- Subroutines
- Methods that produce no output value.
- Properties
- Methods that sometimes produce an output value and that can sometimes be set to a value, that is, be on the left side of an assignment statement.
- Constructors
- Methods that process objects immediately after the objects are instantiated. Constructors produce no output value.
The declarations for these methods are very similar:
Method declaration syntax
Function <name> - [(parameters)] [Is] <type> - [AllowNullObject] [CurrentRecord <context>] [Callable] - [Public | Private [Shared] ] Subroutine <name> - [(parameters)] - [AllowNullObject] [CurrentRecord <context>] - [Public | Private [Shared] ] Property <name> - [(parameters)] [Is] <type> - [ReadWrite | ReadOnly | WriteOnly] - [AllowNullObject] [CurrentRecord <context>] - [Public | Private [Shared] ] Constructor <name> - [(parameters)] - [Public | Private]
Syntax notes:
- The 'name' for Functions, Subroutines, Properties, and Constructors must follow the same rules as variable names and cannot be the same as another member of the class, whether a variable or method, public or private, shared or non-shared.
- The 'parameters' have the same format as the parameter declarations of complex subroutines. For example:
class car public ... subroutine wash(%detergent is string len 16, - %howLong is float, - %howClean is fixed output) ... end public end class
- Functions and properties require a datatype, that is, an Is clause (though the keyword Is is optional).
- For functions, the datatype is the datatype returned by the function.
- For properties, the datatype is both the datatype returned by the property (when the property is used as an input) and the datatype to which the property is set. For example, suppose class Car had a mileage property:
class car public ... property mileage is float ... end public end class
You could do:
%civic is object car ... %civic:mileage = 32.6 ... %hybridMileage = %civic:mileage + 19
- The 'Optional' keyword indicates that a parameter may be specified on the method invocation or not. Alternatively, 'Default' lets you provide a default parameter value.
- The 'AllowNullObject' keyword on a method declaration indicates that, even though the method operates on an instance of the class (an object), the method may be invoked with a null object variable. Ordinarily, a non-shared method referenced by a null object variable causes a (null-object reference) request cancellation error. Invoking a method even when the object variable is null may be useful in diagnostic code that displays object status or cleanup code.
Since a constructor will always receive the just-created object as the object variable, AllowNullObject is not allowed on constructor declarations. - The 'CurrentRecord' clause (CurrentRecord In File <name>, or CurrentRecord In Group <name>) on a method declaration indicates that the method may only be invoked in a record context (established by a For Each Record loop, for example) for the declared file or group. Furthermore, CurrentRecord establishes a record context within the method, so method statements may reference a record field without having to be wrapped inside a record For loop (such as a For Record CurrentRecord loop).
- The 'Callable' keyword, available only on Function declarations, indicates that the function can be Called, that is, invoked without a target for the result. Such a function may be Called even though it returns a value, and the Call may be explicit or implicit.
class car public ... function wash is float callable ... end public end class ... %beetle is object car ... %beetle:wash
The last line can also be written:
call %beetle:wash
Callable is typically to be used on Functions that perform some action but also return a status or informational value. Since the caller might not care about the informational value, the Callable keyword allows the caller to invoke the Function without a target for the return value.
Without the Callable keyword, an attempt to invoke a Function without a target for the result (the target of an assignment or input to another method or subroutine) causes a compilation error. Not having the Callable option makes sense for Functions whose chief purpose is to return a value, since invoking them without a return value defeats the purpose. For example, if you had:
class cat public function weight is float ... end public end class
It probably wouldn't make sense to do:
%misha is object cat ... %misha:weight
In this case, it is probably best not to put the Callable keyword on the function declaration, and to catch likely typos or misunderstandings at compile-time.
- Properties behave very much like variables, with the exception that they have code that is run when their value is set or retrieved. This behavioral similarity between properties and variables can be formally described as an isomorphism between properties and variables. Practically, this means that a variable can be changed to a property, and vice versa, without worrying about how the member is used outside the class.
The keywords ReadWrite, ReadOnly, and WriteOnly indicate the type of access allowed for the property. The default, ReadWrite, indicates that the property can be both set and retrieved. ReadOnly properties can only be retrieved, and WriteOnly properties can only be set.
ReadOnly properties are functionally equivalent to non-Callable Functions: one can think of a ReadOnly Property as a non-Callable Function, and vice versa, with no change to how the method can be used in applications. This makes the naming of such a method a ReadOnly Property or non-Callable Function primarily a hint to the users or maintainers of a class — a ReadOnly Property, as the name suggests, is a static attribute of an object, while a non-Callable Function is thought of as performing some dynamic operation. Any method that is to be Callable must be a Function rather than a Property. - The 'Public', 'Private', and 'Shared' keywords are optional, but, if present, must match the declaration block type. That is, in a Private block, Private must be indicated; in a Public Shared block, Public Shared must be indicated; and so on.
- Constructors behave like subroutines but cannot be called directly. Instead, they are called by the Janus SOAP User Language Interface when an object is first created.
Declaration block example
In the following example, a class block consists of one public block which contains one variable declaration, one method (subroutine) declaration, and a disallow clause:
class stooge public variable funniness is float subroutine slap(%howHard is float) disallow new end public end class
The Auto keyword on object declarations
While it is not a daunting task to create an instance of an object, sometimes it can be unclear exactly where to instantiate an object. Furthermore, certain objects just do not make sense as nulls, so instantiating them is simply a chore. This chore can be simplified by placing the object instantiation statement immediately after the declaration:
%stringArray is object stringList %stringArray = new
But this mixes code with variable declarations, which might be deemed ugly, at best, and doesn't work, anyway, if the object is actually inside a class — code can't be placed in side a Public, Private, Public Shared, or Private Shared block.
To get around these issues, the 'Auto' keyword was introduced for object variable declarations in Sirius Mods version 6.6. For example:
%stringArray is object stringList auto new
An Auto keyword may be followed by either:
- An enumeration value, for enumeration variables.
- An instantiation method, which currently can only be 'New' (with no parameters), for object
and collection variables (for classes for which New is valid). Any instantiation that requires parameters must be done programmatically rather than on the variable declaration (for more information, see :hdref refid=newcons.).
The automatic instantiation of an object for a variable whose declaration includes Auto New does not use any constructor that may be defined for the object class, even a constructor named 'New'. The instantiation is generic, so to speak.
Auto New is valid for user-defined class object variables only if the class definition includes Allow Auto.
The Auto keyword can be very useful for array-like objects, especially collections. For example, it might seem natural to be able to start adding immediately to a stringlist without actually instantiating it:
%colors is object stringlist auto new %colors:add('Turquoise') %colors:add('Amber') ...
Collections seem particularly suited for being immediately added to:
%itemPrice is collection namedArraylist of float auto new %itemPrice('Widget') = 22.34 %itemPrice('Thingy') = 9.84 %itemPrice('Whatsit') = 64.18 ...
The Auto keyword can also be quite useful for enumerations. An Auto keyword on an enumeration variable acts very much like an Initial clause on a String, Float, or Fixed variable:
%haveWarned is enumeration boolean auto false
While it might be tempting to think about the Auto clause as doing the instantiation or assignment — as if an assignment immediately followed the declaration — this is not quite the way Auto works. Auto waits until the first reference to the variable, as long as it is not an assignment to the variable, before assigning to it. That is, the following declarations do not cause three Stringlist instances to be created:
%red is object stringList %white is object stringList %blue is object stringList
But if somewhat further down, the following statement is executed, a Stringlist instance is automatically created before the Add method is invoked:
%blue:add('Azure')
If the first reference to an Auto object is an assignment:
%red = %blue
the Auto attribute will never be exercised, even if the assignment source is Null, and even if the object is subsequently discarded.
Note: the effect of Auto is restricted to the first reference to a variable in a request.