Using Objects in Programs | Extending a Class |
To write an OO application you need to create your own classes in addition to the ones supplied in the Object COBOL Class Library. This chapter explains the rules for writing class programs, with short examples of code. For full syntax definitions, see your Language Reference.
This chapter is about the rules for writing an Object COBOL class. It discusses three distinct entities:
The source code program defining the class.
The class at run-time.
Created by the class object at run-time.
These three terms are all defined in the Glossary at the back of this book. Don't confuse the class and the class object; they are related but they are not the same thing. It may help if you remember that the class exists as source and executable code files; the class object only exists at run-time when the class is loaded.
A class embodies all the behavior for a class object and the instances of that class. It defines the class and instance data, and class and instance methods. A class also inherits the methods of its superclass, and may optionally inherit the data of its superclass. Whether a class inherits data depends on both the way it is written, and whether its superclass permits its subclasses to inherit data.
Each class program describes the behavior of two different objects:
There is never more than one occurrence of each type of class object in an application, whereas there can be many occurrences of its instances. If you are unsure of this point, review the Objects and Messages Tutorial. The main function of any class object is to create instances, although in some cases a class object has other behavior as well.
A class program is structured as a set of nested programs (see Figure 11-1). The outermost level of the class program contains the data and behavior for the class itself. It can include one or more methods, each of which is a smaller program containing the code for one method.
Figure 11-1: Class program structure
The code block below shows the outline of an example class. An ellipsis (...) shows where code has been omitted, and indentation indicates the levels of nesting.
class-id. Example inherits *> Class identification. from Base. *> Also sets up any *> data inheritance (none *> in this example). environment division. *> Environment division *> header is optional. *> All sections legal in *> MF or ANSI 85 COBOL *> environment division *> are valid, though not *> shown here. ... object section. *> Section containing OO *> specific environment *> information. class-control. *> Class control paragraph *> names the files *> containing the *> executables for each *> class. Example is class "exmp" *> The executable for the *> Example class is in *> file exmp. Base is class "base" CharacterArray is class "chararry" . *> Period terminates paragraph. data division. *> Data division header is *> optional. *> All sections legal in *> MF or ANSI 85 COBOL *> data division are valid, *> though not shown here. ... working-storage section. ...
class-object *> Defines the start of the class *> object. object-storage section. *> Defines class object data ... ... method-id. "new". *> Start of class method "new". ... end method "new". *> End of class method "new". end class-object. *> End of the class object object. *> Start of the code *> defining behavior *> of instances *> of the class. object-storage section. *> Defines instance data. ... method-id. "sayHello".*> Start of instance *> method "sayHello" ... end method "sayHello".*> End of instance method. end object. *> End of code for *> instances. end class Example.
Most Object COBOL programs inherit from a superclass. The only exception to this in the supplied Class Library is the Base class, which is at the root of the inheritance tree for all other classes in the library. When you write your own Object COBOL class programs you will nearly always write them as a subclass either of Base or of another class.
If you do decide to write a class which is not a subclass either of Base or of one of its subclasses, you will not inherit the functions provided by Base. The "new" method for creating new instances is part of the metaclass, Behavior, and is always available, but you will have to implement your own method for destroying unwanted instances ("finalize" in base).
There are two things a class program can inherit from its superclass:
These are dealt with separately in the next two sections.
A class object inherits all the class methods of its superclass, and an instance inherits all the instance methods of its superclass. Since the superclass inherits all the class and instance methods of its superclass, it follows that an object inherits all the methods implemented by superclasses all the way up to the root class (usually Base).
Figure 11-2 shows an Object C' receiving the "setValue" message. Object C' is an instance of class C. The superclass of class C is class B, and the superclass of class B is class A. Classes B and C do not implement instance method "setValue", so the message gets passed all the way up to class A where the code for "setValue" exists.
Instance method "getValue" is implemented in both class B and class A. If C' was sent the "getValue" message, it would use the implementation in class B, as this would be found first. You can always reimplement a method that is already in a superclass when you want to change its behavior for subclasses.
Figure 11-2: Method inheritance
Figure 11-3 shows a case where an instance object gets sent a message not implemented in any of its superclasses. The message gets passed up the inheritance chain until it gets to Base. If Base does not understand the message either, it sends the message "doesNotUnderstand" back to the instance which originally received the message.
Figure 11-3: Trapping messages which are not understood
The "doesNotUnderstand" message then gets passed up the chain until it reaches Base, where the "doesNotUnderstand" method raises an exception which stops the application. If you want to create objects which can handle messages they don't understand, you can reimplement the "doesNotUnderstand" message to trap the error before it gets back up to the Base class.
Figure 11-4 shows what happens when you send a message to a class object which does not understand it. The message is passed to the class methods for all the superclasses in the chain. When it reaches Base, something slightly different happens.
Figure 11-4: Trapping a message not understood by a class object
All class objects are instances of Behavior. Behavior is part of the Class Library, and can be thought of as a metaclass, the instances of which are class objects. If the message is an instance method of Behavior, it is executed. Otherwise, Behavior sends it to the instance methods of Base. It does this because Behavior is a subclass of Base.
If no method is found, then Base sends the "doesNotUnderstand" message back to the object which originally received the message.
A class can optionally inherit the data declared in the object-storage sections of its superclass. The next two sections explain:
Inheritance of data is controlled by two optional clauses in the CLASS-ID header at the beginning of the class.
The first of these, DATA IS PROTECTED, enables subclasses to access the class' own data directly. (There is a synonym for this phrase, DATA IS RESTRICTED). The second of these, WITH DATA, enables a class to access its superclass' data directly. For example:
class-id. B data is protected inherits from A with data.
This says that B inherits all the data in A, and that any subclass of B can also inherit B's data (which would include A's data). When you compile class B, the WITH DATA clause causes the Checker to look for two files: a.cls and a.ins.
The .cls file is a COBOL copyfile containing the data definitions for the class object data and the .ins file is a copyfile containing the data definitions for the instance data. The Checker inserts these automatically at the start of the Object-Storage Sections for class and instance data when it compiles the source code for class B.
The DATA IS PROTECTED clause causes the COBOL Checker to generate two files: b.cls and b.ins. These copyfiles start with all the definitions for inherited data, which is followed by the definitions for any class or instance data declared in class B.
The use of copyfiles to implement data inheritance has the following implications:
You might want to prevent subclasses from inheriting data, in which case you can either omit the DATA IS PROTECTED clause or replace it by DATA IS PRIVATE. This stops the Checker from creating the .cls and .ins copyfiles. The following CLASS-ID header would mean that B still inherited A's data, but that subclasses of B would not be able to inherit B's data:
class-id. B data is private inherits from A with data.
For example, you might code another class which started like this:
class-id. C data is protected inherits from B with data.
When you try to compile the class for C, the Compiler will stop with the following message:
FILE below not found - Stop/Retry/Continue/Alter-path: B.CLS
Unless you can give the Compiler a path where it can find a copy of b.cls, you will have to stop the compilation, and alter the source code for C so that it no longer tries to inherit data from B.
Note: If you change a class from having protected data to private data, the Compiler does not delete its .cls and .ins files if they already exist.
As the previous section explained, when you inherit data from a superclass, the Compiler puts a copy of all the class object data definitions from the superclass in your class Object-Storage section, and a copy of all the instance object data definitions in your instance Object-Storage section.
The diagram in Figure 11-5 shows three classes, A, B and C. C inherits from B without data, and B inherits from A with data. Class A implements instance method "setInDataA" which sets a value into data item instanceItemA. On the right-hand side of the dotted line are three instance objects, A1, B1 and C1, created at run-time by class objects A, B and C.
If object B1 gets the message "setInDataA", the message gets executed by the instance method implemented in A (because B does not implement this method). However, the data item that gets updated is instanceItemA held in instance object B1's storage.
Because class B inherits from A with data, the source code in class B can also manipulate instanceItemA directly. For example, the programmer for class B could code an instance method containing the statement:
move "!" to instanceItemA
Object C1 also has memory allocated for instanceItemA and instanceItemB in its Object-Storage at run-time. However, because it does not inherit with data, the Checker does not know the definitions of these items when Class C is compiled. The value for instanceItemA can only be set by sending class object C the "setInDataA" message.
Coding:
move "!" to instanceItemA
in an instance method of C would be rejected by the Checker at compile-time. The programmer of Class C can set instanceItemA by coding inside an instance method:
invoke self "setInDataA" using "!"
This sends the "setInData" message to instance object C1 at run-time, and it gets passed up the inheritance chain until it reaches class A. The object reference SELF is a reserved name which always refers to the object in which it occurs (see the section Self and Super).
The next three sections explain how you code the part of the class program which defines class object behavior. The Class-Object program is nested inside an Object COBOL class, bracketed by the CLASS-OBJECT and END-CLASS OBJECT headers. The Class-Object program embodies the behavior of the class object, which only exists at run-time when the class is loaded.
Not all classes contain a Class-Object program; the main function of a class object at run-time is to create new instances. This function is often inherited from the superclass. The main reasons for writing your own Class-Object are:
The example below shows an outline of a class object:
class-id. data is protected A inherits from Base. object section. class-control. base is class "base" A is class "a" . data division. ... procedure division. *> The procedure division is optional. ... *> It enables you to write class *> initialization code. exit program. class-object. * The code for the class object starts here object-storage section. ... method-id. "setClItemA". ... end method "setClItemA" ... * More class methods could follow. * The code for the class object ends here. end class-object. object. ...
end object. end class "A".
You declare class object data in the Object-Storage Section of the class object. Class object data can only be accessed from class methods and can optionally be inherited by a subclass.
Figure 11-6: Class data in source code and at run-time
When a class is loaded by an application (see the section Class Initialization), the run-time system allocates an area of memory for the data declared in the Object-Storage Section, and for data declared in superclasses. However, a class object can only access data directly which it has declared itself or inherited.
Each class object has its own unique copy of the data declared in its superclasses, whether or not it inherits with data. Figure 11-6 shows three classes, A, B and C, which declare class object data items and the class object data at run-time. This contrasts with some other object models, where each class object would share the inherited data with its superclass.
A class object can only access data it has not inherited by sending messages to its superclass. Data inheritance at run-time is explained in the section Data Inheritance at Run-time. Sending messages to yourself or your superclass is explained in the section Self and Super.
Class initialization code is executed when the class is loaded by the RTS; it is only ever executed once during an application run. Class initialization code is optional, and for most classes is not required.
You can use it for initializing class object data. For example, you might want to make certain objects available to your class object at start-up. You couldn't specify these in value clauses in your class Object-storage section because value clauses can only specify static data, where as object handles are allocated dynamically at run-time.
Object COBOL uses a system of demand loading to reduce application start-up times. This means you can't predict exactly when in an application run any particular class will be loaded. A class is guaranteed to be loaded and initialized by the time it receives its first message during an application run. Because you do not know exactly when a class is loaded, you should not rely on class initialization code to set values in external variables used elsewhere in the application.
Classes are loaded on demand, as required. A class is guaranteed to be loaded and initialized by the time it receives its first message during an application run. Because you do not know exactly when a class is loaded, you should not rely on class initialization code to set values in external variables used elsewhere in the application.
Class methods are programs nested inside the class object. There is only ever one occurrence of any particular type of class object when you run an application, so class methods are generally concerned with the creation and management of instance objects.
Class methods have access to class object data and to all the shared data declared in the class program data division.
You code class and instance methods in the same way; the only difference between them is their position in the class program source code and the scope of data to which they have access. Coding methods is explained in more detail in the section Methods.
The next three sections explain how you code the part of the class program which defines instance object behavior. The example code below shows where the code for instance object behavior fits inside the class:
class-id. A data is protected inherits from Base. object section. class control. base is class "base" A is class "a" data division. working-storage. ... *-----Code for class object class object. object-storage section. ... method-id. "setClItemA" ... end method "setClItemA" ... *> Other methods might follow end class-object. *-----End of code for class object *-----The instance object code starts here. object. object-storage section. *> Instance data goes here. 01 inItemA pic x. *> An example declaration ... *--------Start of methods. method-id. "setInItemA" ... end method "setInItemA". *--------More methods could follow ... end object. *-----the instance object code ends here. end class "A".
The OBJECT and END OBJECT headers mark the start and end of the nested program for object instances.
You declare instance data in the Object-Storage Section of the instance object program. Instance data can only be accessed from instance methods, and each instance can only see its own instance data. Instance data can also optionally be inherited by instances of subclasses.
When an instance is created by an application (see the section Instance Creation Methods), the run-time system allocates an area of memory for the data declared in the Object-Storage Section, and for instance data declared in superclasses. However, an instance object can only access data directly which it has declared itself or inherited.
The only way an instance object can access data it has not inherited, is to send a message to its superclass. Data inheritance at run-time is explained in the section Data Inheritance at Run-time, and sending messages to yourself or your superclass is explained in the section Self and Super.
You should only declare as instance data the data which is actually part of an object's attributes. You should declare data items which you need for temporary working and calculations elsewhere. The safest place to declare temporary data is in the Local-Storage Section of any methods which require it.
For example, the only instance data declared for the Account objects used by the Object COBOL tutorials in this book is the customer name, account number and balance. If there is a piece of data which is the same for all instances (for example, the interest rate), it is declared as class object data. All temporary data is declared in method Local-Storage Sections as required.
Every byte of storage you declare as instance data is allocated every time your application creates a new instance object, or a new instance of a subclass of your class. Only declaring as instance data the data which is strictly part of the object's attributes keeps memory overhead to a minimum.
How you code the initialization for an object is largely up to you. There is no explicit mechanism for instance initialization in the way there is for class initialization. The easiest and most readable way is to code an "initialize" method, and to write an instance creation method (which is always a class method) which invokes the "initialize" method after it has created an instance.
Some objects might be initialized from data supplied with the message that creates them. The creation method would have to pass these parameters to the "initialize" method. Other types of object are always initialized to the same state at creation.
Windows and dialog boxes usually fall into this category; the initialization code usually consists of painting them with the same set of gadgets and labels every time. One place to declare initialization data used by every instance of a particular class, is in the Working-Storage Section at the top of the class.
You can put the data you want into value clauses for your Working-Storage items, the data items are directly accessible by name by all object instances, and the data area is only allocated once, when the class is loaded.
Note: This use of Working-Storage is a Micro Focus feature, and not currently part of the proposed ANSI standard for OO COBOL.
Instance methods are nested programs, inside the instance object program. Instance methods are the way in which an application can alter the state of an object or query its attributes.
Instance methods have access to instance data and to all the shared data declared in the class program data division.
You code class and instance methods in the same way; the only difference between them is their position in the class program source code and the scope of data to which they have access. Coding methods is explained in more detail in the section Methods.
Each method is a nested program bracketed by METHOD-ID and END METHOD headers. You may optionally precede the METHOD-ID by an IDENTIFICATION DIVISION header. Class methods appear inside the class object program. Instance methods appear inside the instance object program itself (between the OBJECT and END OBJECT headers), following the Object-Storage Section.
The following sections look at the following aspects of coding methods:
Class methods can access class Object-Storage data and instance methods can access instance Object-Storage data. In all other respects class and instance methods look and work identically. Both types of method can access the shared data declared in the class program data division.
A method can also have data of its own; you can declare the following types of storage section in a method:
Data items which are common to all invocations of the method, and to all
object instances.
Data items which are local to the current invocation of a method.
The following piece of code shows an example method, "setInItemA" which stores a single character and converts it to upper-case. The method returns 1 if the character was already upper-case and zero otherwise. It refers to a data item, clItemA, which you can assume is declared in object-storage.
method-id. "setInItemA". local-storage section. 01 aTempItem pic x(4) comp-5. linkage section. 01 lnkValue pic x. 01 aResult pic x comp-5. procedure division using lnkValue returning aResult. move lnkValue to inItemA move 1 to aTempItem call "CBL_TOUPPER" using inItemA aTempItem if lnkValue = clItemA move 1 to aResult else move 0 to aResult exit method. end method "setInItemA".
The method uses Local-Storage for all its temporary working data. The RTS allocates memory for Local-Storage each time the method is invoked, and de-allocates it after the exit method. This has the advantage of supporting recursion. A method may be recursive even if it doesn't invoke itself directly; it may invoke a method in a different object which invokes the original method a second time.
You can use Working-Storage for temporary variables inside a method, but this remains permanently allocated, and is shared by all invocations of the method, in all objects of the same class. This means that it is not suitable for any method that could be invoked recursively.
For example, method "setData" in object A1 (an instance of class A) invokes method "getDetails" in object B1. The method in object B1 does a "setData" in object A2 (also an instance of class A). Any data in Working-Storage from the original invocation of "setData" (which has not yet finished execution) is overwritten by the second invocation. Local-Storage data for the two invocations of "setData" would be kept completely separate.
The Linkage section is used to declare all parameters passed to or from the method. Usually parameters are passed by reference, which means that no actual memory is allocated for linkage section items.
Quite often a class or instance object may need to send a message to itself. The two most common reasons are listed below:
There are three reserved object reference names which are available to class programs and initialized by the run-time system when an object is created:
Contains the object handle of the object in which it occurs. If you send a message to self from object A, object A is the receiver of the message; if you send a message to self from object C, object C is the receiver of the message.
SELF always refers to the object currently executing, even if the method is one which is being inherited. For example, imagine two objects, A' and B', instances of classes A and B where B inherits from A. A implements instance method, "calculateValue". The "calculateValue" method sends the message "getObjectConstant" to self. If B' gets sent the "calculateValue" message, the method is inherited from class A. When "calculateValue" sends a message "getObjectConstant", the message actually gets sent to B'.
If A' were to be sent the "calculateValue" message, then the send to self would send the message to A'.
Syntax which enables an object to send a message to itself, but starts looking for methods in the code of its superclass. If used from an instance, the run-time system searches for a method beginning with the instance code of the superclass, and works its way up through the instance methods of all the superclasses until it finds a method matching the message. If used from a class method, the run-time system searches for a class method beginning with the class object code of the superclass and works its way up through the class methods of all the superclasses until it finds a method matching the message.
Contains an object handle for the class object for this current object. Enables an instance to send a message to the class object which created it. If you use SELFCLASS from a class method, it points to the metaclass for this class, which is an instance of Behavior.
Note: You can also use SELF or SELFCLASS as object reference data items which you can pass as parameters to other methods. You can only use SUPER as the target object in an invoke statement.
You need to be able to create instance objects from your classes. At its simplest, creating a new object instance means getting a memory allocation for its data and an object handle by which you can refer to it. This basic task is handled by an instance method in the metaclass, Behavior, called "new". All class objects are instances of the metaclass, and so inherit this method.
If you do not want to carry out any initialization for an instance, you do not need to code anything in the class for its creation; you can simply inherit the "new" method from the metaclass. The example class below works like this:
class-id. simple inherits from base. * No class methods for simple ... object. object-storage section. 01 simpleInstanceData pic x(80). * Instance methods for simple are not shown here. ... end object. end class simple.
When your application wanted a new instance of Simple, you would code:
invoke simple "new" returning anObject
where anObject is a data item of type object reference. The "new" method allocates the 80 bytes required for an instance of simple and puts an object handle into anObject.
In many cases you will want to carry out some instance initialization on objects created, in which case you have to code your own class method for creating objects. However, your own instance creation methods will always use Behavior method "new" as the basic mechanism for creating an object and allocating memory. For example:
class-id. A data is protected inherits from Base. object section. ... method-id. "newWithData" linkage section. 01 lnkObject object reference. 01 lnkName pic x(80). procedure division using lnkName returning lnkObject. *----Create a new instance of A using the "new" method from Base invoke super "new" returning lnkObject *----Send it an initialize message. invoke lnkObject "initialize" using lnkName exit method. end method "newWithData". object. object-storage section. 01 theName pic x(80). ... method-id. "initialize" linkage section. 01 lnkName pic x(80).
procedure division using lnkName. *----Store the initialization parameter in the object's * instance data move lnkName to theName exit method. end method "setInItemA". ... end object. end class "A".
The "newWithData" method in class A sends the "new" message to SUPER, introduced in the section Self and Super. Sending a message to SUPER is very similar to sending it to SELF; the only difference is that the run-time system starts looking for a matching method in the code of your superclass rather than in the code of this class.
There is no rule which says that you have to name an instance creation method "new", although it is used by convention as it makes it obvious what the method does.
All Object COBOL classes use five reserved data names, which are always initialized by the run-time system :
This contains the object handle for the class.
You do not have to declare these variables to use them; they are inserted into a class by the Checker.
The Object COBOL system also uses one external variable, OO-Desktop. This is used with the GUI classes in the supplied class library. Do not change the value of this variable in Object COBOL programs.
You can refer to these objects in your programs, and send messages to them, but must not change their contents.
Copyright © 1999 MERANT International Limited. All rights reserved.
This document and the proprietary marks and names
used herein are protected by international law.
Using Objects in Programs | Extending a Class |