The methods of E are very similar to normal procedures, but there is one, big difference: a method is part of a class, so must somehow be identified with the other parts of the class.
In E this identification is done by relating all methods to the corresponding
OBJECT type for the class, using the
OF keyword after the description of the method's parameters.
So, the methods of the simple set class would be defined as outlined below (of course, these examples have omitted the code of methods).
PROC add(x) OF set /* code for add method */ ENDPROC PROC member(x) OF set /* code for member method */ ENDPROC PROC empty() OF set /* code for empty method */ ENDPROC PROC union(s:PTR TO set) OF set /* code for union method */ ENDPROC
At first sight it might seem that the particular
set object which would be manipulated by these methods is missing from the parameters.
For instance, it appears that the
empty method should need an extra
PTR TO set parameter, and that would be the
set object it tested for emptiness.
However, methods are called in a slightly different way to normal procedures.
A method is a part of a class, and is called in a similar way to accessing the data elements of the class.
That is, the method is selected using `.' and acts (implicitly) on the object from which it was selected.
The following example shows the allocation of a set object and the use of some of the above methods.
DEF s:PTR TO set NEW s -> Allocate an OOP object s.add(17) s.add(-34) IF s.empty() WriteF('Error: the set s should not be empty!\n') ELSE WriteF('OK: not empty\n') ENDIF IF s.member(0) WriteF('Error: how did 0 get in there?\n') ELSE WriteF('OK: 0 is not a member\n') ENDIF IF s.member(-34) WriteF('OK: -34 is a member\n') ELSE WriteF('Error: where has -34 gone?\n') ENDIF END s -> Finished with s now
This is why the methods do not take that extra
PTR TO set argument.
If a method is called then it has been selected from an appropriate object, and so this must be the object which it affects.
The slightly complicated method is
union which adds another
set object by unioning it.
In this case, the argument to the method is a
PTR TO set, but this is the set to be added, not the set which is being expanded.
So, how do you refer to the object which is being affected?
In other words, how do you affect it?
Well, this is the remaining difference from normal procedures: every method has a special local variable,
self, which is of type
PTR TO class and is initialised to point to the object from which the method was selected.
Using this variable, the data and methods of object can be accessed and used as normal.
For instance, the
empty method has a
self local variable of type
PTR TO set, and can be defined as below:
PROC empty() OF set IS self.size=0
Constructors are simply methods which initialise the data of an object.
For this reason they should normally be called only when the object is allocated.
NEW operator allows OOP objects to call a constructor at the point at which they are allocated, to make this easier and more explicit.
The constructor will be called after
NEW has allocated the memory for the object.
It is wise to give constructors suggestive names like
copy, or the same name as the class.
The following constructors might be defined for the set class:
/* Create empty set */ PROC create() OF set self.size=0 ENDPROC /* Copy existing set */ PROC copy(oldset:PTR TO set) OF set DEF i FOR i:=0 TO oldset.size-1 self.elements[i]:=oldset.elements[i] ENDFOR self.size:=oldset.size ENDPROC
They would be used as in the code below.
Notice that the
create constructor is, in this case, redundant since
NEW will initialise the data elements to zero.
NEW does sufficient initialisation then you do not have to define any constructors, and even if you do have constructors you don't have to use them when allocating objects.
DEF s:PTR TO set, t:PTR TO set, u:PTR TO set NEW s.create() IF s.empty THEN WriteF('s is empty\n') END s NEW t /* This happens to be the same as using create */ IF t.empty THEN WriteF('t is empty\n') t.add(10) NEW u.copy(t) IF u.member(10) THEN WriteF('10 is in u\n') END t, u
For each class there is at most one destructor, and this is responsible for clearing up and deallocating resources.
If one is needed then it must be called
end, and (as this might suggest) it is called automatically when an OOP object is deallocated using
So, for OOP objects with a destructor, the (roughly) equivalent code to
Dispose is a bit different.
Take care to note that the destructor is not called if
END is not used to deallocate an OOP object (i.e., if deallocation is left to be done automatically at the end of the program).
END p IF p p.end() -> Call destructor Dispose(p) p:=NIL ENDIF
The simple implementation of the set class needs no destructor.
If, however, the
elements data were a pointer (to
LONG), and the array were allocated based on some size parameter to a constructor, then a destructor would be useful.
In this case the set class would also need a
maxsize data element, which records the maximum, allocated size of the
OBJECT set elements:PTR TO LONG size maxsize ENDOBJECT PROC create(sz=100) OF set -> Default to 100 DEF p:PTR TO LONG self.maxsize:=IF (sz>0) AND (sz<100000) THEN sz ELSE 100 self.elements:=NEW p[self.maxsize] ENDPROC PROC end() OF set DEF p:PTR TO LONG IF self.maxsize=0 WriteF('Error: did not create() the set\n') ELSE p:=self.elements END p[self.maxsize] ENDIF ENDPROC
Without the destructor
end, the memory allocated for
elements would not be deallocated when
END is used, although it would get deallocated at the end of the program (in this case).
AllocMem were used instead of
NEW to allocate the array, then the memory would have to be deallocated using
FreeMem, and this would best be done in the destructor, as above.
(The memory would not be deallocated automatically at the end of the program if
AllocMem is used.)
Another solution to this kind of problem would be to have a special method which called
FreeMem, and to remember to call this method just before deallocating one of these objects, so you can see that the interaction of
END with destructors is quite useful.
Already, the above re-definition of
set begins to show the power of OOP.
The actual implementation of the set class is very different, but the interface can remain the same.
The code for the methods would need to change to take into account the new
maxsize element (where before the fixed size of 100 was used), and also to deal with the possibility the
create constructor had not been used (in which case
elements would be
But the code which used the set class would not need to change, except maybe to allocate more sensibly sized sets!
Yet another, different implementation of a set was outlined above (see 16.3 Binary Trees).
In fact, remarkably few changes would be needed to convert the code from that section into another implementation of the set class.
new_set procedure is like a set constructor which initialises the set to be a singleton (i.e., to contain one element), and the
add procedure is just like the
add method of the set class.
The only slight problem is that empty sets are not modelled by the binary tree implementation, so it wouldn't, as it stands, be a complete implementation.
It would be straight-forward (but unduly complicated at this point) to add support for empty sets to this particular implementation.
Go to the Next or Previous section, the Detailed Contents, or the Amiga E Encyclopedia.