Discussion paper by Peter Elderon.
From comp.lang.pl1 Sat Nov 23 14:13:20 1996 From: Dave JonesNewsgroups: comp.lang.pl1 Subject: Ptere Elderon's OO-PL/I presentation. Date: Mon, 18 Nov 1996 13:51:04 -0600 Organization: NeoSoft, Inc.
In PL/I, class users and subclasses need not be recompiled if
PL/I also clearly separates the client's view of a class from the implementer's by defining a class via:
DEFINE
CLASS
INHERITS ( )
METHODS ( ) ;
The INHERITS clause names this class's parent. Every PL/I
class is a subclass of the PL/I-provided Object class (ex-
cept, of course, the Object class itself). Note also that a
class may have only one parent; like Smalltalk and Java,
object-oriented PL/I supports only single inheritance.
The METHODS clause names the operations permitted on objects of this class. These methods are the only way a client can manipulate objects of this class. There is no public or pro- tected data: all data is encapsulated by methods.
For example, the following statement defines a "student" class
define
class
student
inherits( object )
methods
(
setUp entry( char(64) varying,
char(6) )
,get_Id entry() returns( char(6) )
,get_Name entry() returns( char(64) varying )
,print entry()
);
The name appearing in the specification of a HANDLE attri- bute may be the name specified in a DEFINE STRUCTURE state- ment or in a DEFINE CLASS statement. A variable declared as a HANDLE for a class is also known as an instance of that class.
Just as the only access to the elements in a defined struc- ture is via the => operator applied to a handle to that structure, the only access to the methods in a defined class is is via the => operator applied to a handle to that class.
For instance, given the student class in the example above, the following statements declare an instance of that class and invoke the setup method for that class.
dcl s handle student;
call s=>setUp( 'Z. Dobson', '030507' );
The example above is incomplete since before you apply a
method to an object, the object must be instantiated. The
primary way to instantiate an object is via the NEW type
function applied to the name of that class. So the example
above could be completed to look like:
dcl s handle student;
s = new(: student :);
call s=>setUp( 'Z. Dobson', '030507' );
So just as for defined structures, storage for defined
classes is obtained via the new type function.
If an instance of a class is obtained via the new function,
it should be destroyed by applying to it the delete method
from the PL/I object class. So the student instance ob-
tained above could be destroyed via
call s=>delete();
THE IMPLEMENTER'S VIEW OF A CLASS
A PL/I package implements a class by specifying the name of
the class in the OWNS clause of the PACKAGE statement. For
instance, the following statement indicates that this pack-
age implements the student class:
student:
package
owns( student );
A package that owns a class must contain a DEFINE CLASS
statement for that class.
A package that owns a class must also define any private
data for a class via the DEFINE INSTANCE statement:
DEFINE
INSTANCE
( ) ;
The DEFINE INSTANCE statement is valid only inside a package
that owns the class whose data that it defines.
The declaration commalist in the define instance statement
must specify must specify either a scalar or a structure or
an array thereof.
QUESTION: should a list of scalars and/or structures be allowed??
A package that owns a class must implement all the methods
in that class by containing a level-one procedure for each
method defined by the class. Note that these procedures are
not exported from the package; they cannot be invoked from
outside or inside the package except via a handle.
As indicated earlier, PL/I allows new methods and variables
to be added to a parent class without "damaging" any of is
subclasses or clients. This implies that classes must be
created at run-time. The compiler generates all the code to
do this. The actual creation of the class occurs in the
PL/I package that owns the class.
The code generated by the compiler produces several external
names built using the name of the class:
This name is generated only if the PL/I object class is the SOM object class
student:
package
owns( student );
%include class;
%include student;
dcl self builtin;
define
instance
student
( 1 student_data
,2 student_id char(6)
,2 student_name char(64) varying
)
;
setUp: proc( name, id );
dcl name char(64) varying;
dcl id char(6);
self=>student_id = id;
self=>student_name = name;
end;
get_Id: proc returns( char(6) );
return( self=>student_id );
end;
get_Name: proc returns( char(64) varying );
return( self=>student_name );
end;
print: proc;
put skip edit( ' id', ': ',
self=>student_id ) (a(12),a,a );
put skip edit( ' name', ': ',
self=>student_name ) (a(12),a,a );
end;
end student;
A package that owns a class may also override methods in its
parents' classes. The DEFINE OVERRIDES statement indicates
which parent methods a class will override:
DEFINE
OVERRIDES
( ) ;
The DEFINE OVERRIDES statement is valid only inside a pack-
age that owns the class named in it. This package must also
contain a level-one procedure for each method overridden by
the class.
A method may often want to override a parent method in order to perform some special processing before or after the normal processing performed by the parent method. In order to be able to do this, it must be possible to specify that the method that should be applied to self is that defined by its parent. The PARENT built-in function provides this ability.
For instance, if a subclass of the student class overrode the student print method because it wanted to print addi- tional data belonging to the subclass, the first thing the subclass print method might want to do is invoke the student print method as in the following example:
print: proc;
call parent=>print();
/* use self to print additional data */
end;
The object class define statement is
define
class
object
methods
(
delete entry()
/* used to destroy an instance of a class */
/* it will invoke the uninit method */
, init entry()
/* invoked when an instance is created */
/* the class object version does nothing */
/* but exist in order to be overridden */
, uninit entry()
/* invoked when an instance is destroyed */
/* the class object version does nothing */
/* but exist in order to be overridden */
);
For example, given the student class with two derived classes, undergrad and graduate, consider
dcl s handle student;
dcl u handle undergrad;
dcl g handle graduate;
...
s = u; /* valid */
u = s; /* valid */
...
s = g; /* valid */
u = s; /* invalid */
A new condition CONFORMANCE, which can be disabled or enabled
like SUBSCRIPTRANGE, will be raised when enabled if
such an invalid assignment is attempted.
In examples used above, the student class has a method, setUp, that can be used to initialize its instance data. However, the student class could be defined with a constructor that would be invoked to perform this initialization.
If a method list for a class contains a method with the same name as the class name, that method is called a constructor. The constructor will be invoked when the new function is invoked, and if the constructor requires parameters, the new function must be invoked with the necessary additional arguments. For instance, the student class above could be defined via
define
class
student
inherits( object )
methods
(
student entry( char(64) varying,
char(6) )
,get_Id entry() returns( char(6) )
,get_Name entry() returns( char(64) varying )
,print entry()
);
The student class would then be instantiated via
dcl s handle student;
s = new(: student, 'Z. Dobson', '030507' :);
Constructor methods cannot be invoked directly except a
constructor method itself may invoke via the parent builtin
the constructor for its parent class.
define
class
undergrad
inherits( student )
methods
(
setUp_Undergrad entry( char(64) varying,
char(6),
char(8) )
);
define
class
graduate
inherits( student )
methods
(
setUp_Grad entry( char(64) varying,
char(6),
char(8) )
);
The implementation of the undergrad class could be
undergrad: package owns( undergrad );
%include class;
%include student;
%include undergrad;
dcl self builtin;
dcl parent builtin;
define
instance
undergrad
(
underGradDate char(8)
)
;
define
overrides
undergrad
(
print
)
;
setUp_Undergrad: proc( name, id, graddate );
dcl name char(64) varying;
dcl id char(6);
dcl gradDate char(8);
call self=>setup( name, id );
self=>UnderGradDate = graddate;
end;
print: proc;
call parent=>print;
put skip edit( ' grad date', ': ', self=>undergradDate )
( a(12), a, a );
end;
end undergrad;
While the implementation of the graduate class could be
graduate: package owns(graduate);
%include class;
%include student;
%include graduate;
dcl self builtin;
dcl parent builtin;
define
instance
graduate
(
gradDegree char(8)
)
;
define
overrides
graduate
(
print
)
;
setup_Grad: proc( name, id, degree );
dcl name char(64) varying;
dcl id char(6);
dcl degree char(8);
call self=>setup( name, id );
self=>GradDegree = degree;
end;
print: proc;
call parent=>print;
put skip edit( ' degree', ': ', self=>gradDegree )
( a(12), a, a );
end;
end graduate;
The following code is client code using these classes. It obtains an instance of the undergrad class, initializes it and saves the handle for that instance in an array of stu- dent handles. It then obtains an instance of the graduate class, initializes it and saves the handle for that instance in the same array of student handles. Finally it loops through that array and prints the data for each student. The print function is polymorphic since the two subclasses have
each overridden it: when applied to a student handle it will call the print method for the undergrad or graduate class in accordance with the class to which the handle actually points.
So the output of the following program would be, thanks to polymorphism:
id : 030507
name : Z. Dobson
grad data : 19550611
id : 111317
name : L. Jim
degree : Ph. D.
registrar: proc;
%include class;
%include student;
%include undergrad;
%include graduate;
dcl s(20) handle student;
dcl u handle undergrad;
dcl g handle graduate;
dcl c fixed bin(31) init(0);
dcl jx fixed bin(31);
/* add a sample undergrad student */
c = c + 1;
u = new(: undergrad :);
call u=>setUp_Undergrad( 'Z. Dobson', '030507', '19550611' );
s(c) = u;
/* add a sample graduate student */
c = c + 1;
g = new(: graduate :);
call g=>setUp_Graduate( 'L. Jim', '111317', 'Ph. D.' );
s(c) = g;
/* print out all the students */
do jx = 1 to c;
call s(jx)=>print();
put skip;
end;
end;