Object Oriented Programming |
|
|
QMBasic includes support for object orientated programming. Users familiar with other object oriented languages will find that QM offers many of the same concepts but, because they are integrated into an existing programming environment, there may be some significant differences in usage.
What is an Object?
An object is a combination of data and program operations that can be applied to it. An object is defined by a class module, a QMBasic program that is introduced by the CLASS statement and contains the definitions of persistent data items and public subroutines and functions. An object is a run time instance of the class, instantiated by use of the OBJECT() function OBJ = OBJECT("MYCLASS") where "MYCLASS" is the catalogue name of the class module. The OBJ variable becomes a reference to an instance of the class.
A second use of the OBJECT() function with the same catalogue name will create a second instance of the object. On the other hand, copying the object variable creates a second reference to the same instance. A clone of an existing object can be created by use of the OBJECT() function in which the argument is a reference to the object that is to be cloned.
There are six styles of data storage available in a class module.
Public variables are declared using the PUBLIC statement, for example, PUBLIC A, B(5) at the start of the class module before any executable program statements. Each instance of a class has its own separate public variables but the values persist between entries to that object instance. Public variables are initially unassigned and may be accessed from within the class module or, subject to rules set out below, from outside.
Private variables are declared using the PRIVATE statement, for example, PRIVATE C, D, E at the start of the class module before any executable program statements. Each instance of a class has its own separate private variables but the values persist between entries to the object instance. Private variables are initially unassigned and may be accessed only from within the class module.
Restricted variables are declared using the RESTRICTED statement, for example, RESTRICTED C, D, E at the start of the class module before any executable program statements. They are identical to public variables except that they are only visible to objects that appear in the inheritance tree of the object referencing them.
Shared variables are declared using the SHARED statement, for example, SHARED PUBLIC F, G(10), H at the start of the class module before any executable program statements. Each instance of the same class shares the same variables. Values stored in shared variables persist until the last reference to the class is discarded. Shared variables are initially unassigned and may be defined as either private or public.
Common variables are declared using the COMMON statement, for example, COMMON I, J(8) or COMMON /NAME/ K, L at the start of the class module before any executable program statements. As with use of common variables in other program module types, common variables are accessible to all programs, subroutines, functions and objects within the QM process. Common variables are initially set to zero unless the UNASSIGNED.COMMON setting of the $MODE compiler directive is enabled. Variables in an unnamed common are discarded on return to the command processor, those in named common persist until the QM session terminates or the common is explicitly deleted using the DELETE.COMMON command.
Local variables are either not declared at all or are declared using a DIMENSION (DIM) statement. As in other program types, they exist only while the object instance in which they appear is active and are discarded on leaving the program. Local variables are accessible only from within the object instance and are initially unassigned.
Private and public data items are frequently used to store what other object oriented programming environments would term property values. These data items are initially unassigned unless an initialisation value is present, for example PRIVATE WIDTH = 80 which will set WIDTH to 80 prior to execution of the CREATE.OBJECT subroutine described below.
Public Functions and Subroutines
Another important difference between class modules and other program types is that a class module usually has multiple entry points, each corresponding to a public function or public subroutine. Simply calling the class module by its catalogue name will generate a run time error.
Just as with conventional QMBasic functions and subroutines, a public function must return a value to its caller whereas a public subroutine does not (though it can do so by updating its arguments).
A public function is defined by a group of statements such as PUBLIC FUNCTION XX(A,B,C) ...processing... RETURN Z END where XX is the function name, A, B and C are the arguments (optional), and Z is the value to be returned to the caller.
A public subroutine is defined by a group of statements such as PUBLIC SUBROUTINE XX(A,B,C) ...processing... RETURN END where XX is the subroutine name and A, B and C are the arguments (optional). A whole matrix can be referenced as an argument by following it with the dimension values. For example, PUBLIC SUBROUTINE CALC(CLIENT, CLI.REC(1), TOTVAL) In this example, the dimension value has been shown as 1 to emphasise that the actual value is irrelevant. The compiler uses this purely to determine that CLI.REC is a single dimensional matrix, possibly representing a database record read using MATREAD. The alternative syntax used with SUBROUTINE statements by prefixing the matrix name with MAT and using a DIMENSION statement to set dimensionality is not available for public subroutines and functions.
The number of arguments in a public function or subroutine is normally limited to 32 but this can be increased using the MAX.ARGS option of the CLASS statement.
Both styles of public routine allow use of the VAR.ARGS qualifier after the argument list to indicate that it is of variable length. Argument variables for which the caller has provided no value will be unassigned. The ARG.COUNT() function can be used to find the actual number of arguments passed. A special syntax of three periods (...) used as the final argument specifies that unnamed scalar arguments are to be added up to the limit on the number of arguments. These can be accessed using the ARG() function and the SET.ARG statement. See the PUBLIC statement for more details of this feature.
It is valid for a class module to contain combinations of a PUBLIC variable, PUBLIC SUBROUTINE and PUBLIC FUNCTION with the same name. If there is a public subroutine of the same name as a public variable, the subroutine will be executed when a program using the object attempts to set the value of the public item. If there is a public function of the same name as a public variable, the function will be executed when a program using the object attempts to retrieve the value of the public item. If both are present, the public property variable will never be directly visible to programs using the object.
Sometimes an application developer may wish a public variable to be visible to users of the class for reading but not for update. Although this could be achieved by use of a dummy PUBLIC SUBROUTINE that ignores updates or reports an error, public variables may be defined as read-only by including the READONLY keyword after the variable declaration: PUBLIC A READONLY or PUBLIC B(5) READONLY
Restricted Functions and Subroutines
These are declared using the RESTRICTED statement and are identical to public items subject to the inheritance rule described above for restricted variables.
Referencing an Object
References to an object require two components, the object variable and the name of a property or method within that object. The syntax for such a reference is OBJ->PROPERTY or, if arguments are required, OBJ->PROPERTY(ARG1, ARG2, ...)
Any argument may reference a whole matrix by prefixing the matrix name with the keyword MAT, for example OBJ->CALC(CLIENT, MAT CLI.REC, TOTVAL)
The names of public variables, subroutines and functions in object references are case insensitive, even if the class module is compiled with the CASE.SENSITIVE setting of the $MODE compiler directive.
When used in a QMBasic expression, for example, ITEMS += OBJ->LISTCOUNT the object reference returns the value of the named item, in this case LISTCOUNT. This may be a public variable or the value of a public function. If the same name is defined as both, the public function is executed.
When used on the left of an assignment, for example, OBJ->WIDTH = 70 the object reference sets the value of the named item, in this case WIDTH. This may be a public variable or the value of a public subroutine that takes the value to be assigned as an argument. If the same name is defined as both, the public subroutine is executed.
This dual role of public variables and functions or subroutines makes it very easy to write a class module in which, for example, a property value may be retrieved without execution of any program statements inside the object but setting the value executes a subroutine to validate the new value.
Using Dimensions and Arguments
Public variables may be dimensioned arrays. Subscripts for index values are handled in the usual way: OBJ->MODE(3) = 7 where MODE has been defined as a single dimensional array. If MODE has an associated public subroutine, the indices are passed via the arguments and the new value as the final argument. Thus, if MODE was defined as PUBLIC SUBROUTINE MODE(A,B) the above statement would pass in A as 3 and B as 7.
Execution of Object Methods
Other object oriented languages usually provide methods, subroutines that can be executed from calling programs to do some task. QMBasic class modules do this by using public subroutines. The calling program uses a statement of the form: OBJ->RESET where RESET is the name of the public subroutine representing the method. Again, arguments are allowed: OBJ->RESET(5)
This leads to an apparent syntactic ambiguity between assigning values to public properties and execution of methods. Actually, there is no ambiguity but the following two statements are semantically identical: OBJ->X(2,3) OBJ->X(2) = 3
Expressions as Property Names
All of the above examples have used literal (constant) property names. QMBasic allows expressions as property names in all contexts using a syntax OBJ->(expr) where expr is an expression that evaluates to the property name.
Object References in Subroutine Calls
Any reference to an object element in a subroutine call, for example CALL SUBNAME(OBJ->VAR) is considered to be read access and is passed by value. If the subroutine updates the argument, this will not update the object property value.
The ME Token
Sometimes an object needs to reference itself. The reserved data name ME can be used for this purpose: ME->RESET Note that setting a private or public variable inside the object to the ME reference, for example PUBLIC MYSELF MYSELF = ME would create a situation where the object can never be discarded as there is always a reference to it.
The CREATE.OBJECT Subroutine
When an object is instantiated using the OBJECT() function, part of this process checks whether there is a public subroutine named CREATE.OBJECT and, if so, executes it. This can be used, for example, to preset default values in public and private variables. Up to 32 arguments may be passed into this subroutine by extending the OBJECT() call to include these after the catalogue name of the class module.
The DESTROY.OBJECT Subroutine
An object remains in existence until the last object variable referencing it is discarded or overwritten. At this point, the system checks for a public subroutine named DESTROY.OBJECT and, if it exists, it is executed. This subroutine is guaranteed to be executed, even if the object variable is discarded as part of a program failure that causes an abort. The only situation where an object can cease to exist without this subroutine running to completion is if the DESTROY.OBJECT subroutine itself aborts.
The MAIN Subroutine
If a class module includes a public subroutine named MAIN, an object instantiated from that class can be executed as a main program from the command processor using RUN or a catalogue reference in exactly the same way as a PROGRAM module but cannot be executed using CALL. When used in this way, the CREATE.OBJECT subroutine, if present, will be executed followed by the MAIN subroutine.
The UNDEFINED Name Handler
The optional UNDEFINED public subroutine and/or public function can be used to trap references to the object that use property names that are not defined. This handler is executed if a program using the object references a name that is not defined as a public item. The first argument will be the undefined name. Any arguments supplied by the calling program will follow this. The ARG.COUNT() and ARG() functions can be used to help extract this data in a meaningful way.
If there is no UNDEFINED subroutine/function, object references with undefined names cause a run time error.
Inheritance
Sometimes it is useful for one class module to incorporate the properties and methods of another. This is termed inheritance.
Use of the INHERITS clause of the CLASS statement effectively inserts declaration of a private variable of the same name as the inherited class (removing any global catalogue prefix character) and adds name = OBJECT(inherited.class) INHERIT name to the CREATE.OBJECT subroutine.
Alternatively, inheritance can be performed during execution of the object by direct use of the INHERIT statement.
The name search process that occurs when an object is referenced scans the name table of the original object reference first. If the name is not found, it then goes on to scan the name tables of each inherited object in the order in which they were inherited. Where an inherited object has itself inherited further objects, the lower levels of inheritance are treated as part of the object into which they were inherited. If the name is not found, the same search process is used to look for the undefined name handler.
An inherited object can subsequently be disinherited using DISINHERIT.
Debugging an Object Instance
The names of arguments to a public subroutine are not known to the debugger. Instead, a special positional reference of the form "*Arg1" is needed when displaying argument variables. The *Arg prefix is case insensitive and the argument number may include leading zeroes. For example, display of the second argument could use a debugger command such as /*arg2 All of the qualifiers to variable references such as array indices and dynamic array element positions may be used.
Syntax Summary
CLASS name {INHERITS class1, class2...} PUBLIC A {READONLY}, B(3), C PRIVATE P, Q, R
PUBLIC SUBROUTINE SUB1(ARG1, ARG2) {VAR.ARGS} ...processing... END
PUBLIC FUNCTION FUNC1(ARG1, ARG2) {VAR.ARGS} ...processing... RETURN RESULT END
...Other QMBasic subroutines... END
There is a sample QMBasic class module named INDEX.CLS in the BP file of the QMSYS account, catalogued as !INDEX.CLS. This class allows an application to scan an index one record id at a time instead of one indexed value at a time. Full details of its use and internal operation can be found in the source code.
See also: CLASS, DISINHERIT, INHERIT, OBJECT(), PRIVATE, PUBLIC, RESTRICTED |