Sometimes, one wishes to derive a whole family of classes from a single superclass. The interface for them all is common, but one or more components is known to be different ahead of time for each subclass. Perhaps only one method is different, but in any case there isn't a specific class that can be thought of as ancestor to all the variations, despite the commonality.
In such a situation, the common ancestor class might have the method in question with only a procedure heading and no implementation, with the intention that this (vacuous) method will have to be overridden by a subclass to produce anything useful.
A class that contains unimplemented methods intended to be overridden by implemented methods in a subclass is called an abstract class.
A method that is intended to be overridden and so has no implementation is called a deferred method or an abstract method.
In OOM-2 both the abstract class itself and any deferred method that causes the class to be abstract must be tagged by preceding them with the keyword ABSTRACT. The example that follows has some simple classes that do little more than illustrate these rules.
Here, the plan is to declare a number of geometric classes, each of which will have a method to obtain its dimension(s) and methods to compute the area and perimeter. Initially, an abstract class is defined that reveals three abstract methods for these tasks.
DEFINITION MODULE GeoFigures; (* demo of abstract class syntax using plane figures R. Sutcliffe 1998 09 25 *) TRACED ABSTRACT CLASS GeoFigure; REVEAL InitSize, Area, Perim; ABSTRACT PROCEDURE InitSize; ABSTRACT PROCEDURE Area () : REAL; ABSTRACT PROCEDURE Perim () : REAL; END GeoFigure; END GeoFigures.
NOTE: The keyword ABSTRACT , when used, comes immediately before CLASS (and after TRACED, where the latter is used).
In the event that a method is marked as abstract in a definition, it must not be implemented. Indeed, if, as in this case, the declaration will not be implementing any methods at all, the class declaration still has to be present, but can be empty.
IMPLEMENTATION MODULE GeoFigures; TRACED ABSTRACT CLASS GeoFigure; END GeoFigure; END GeoFigures.
For simplicity, several classes have been derived from this abstract one in a single module below. Observe that each gathers the data necessary for its own version of the object and does its own version of the calculation, while maintaining a consistent interface for the whole family of classes. Moreover, the class Square is a subclass of the class Rectangle, which in turn is a specialization of the abstract class GeoFigures above. Square employs only one of the two dimensions defined in Rectangle.
One might be tempted to make the setting of the class attribute variables part of the initialization block of the class, but this is not a good idea in this case, because Square would then end up running both blocks, doing the one for Rectangle first, then its own. Thus, this code has been made a method called from the outside.
MODULE DemoGeos; (* demonstration of subclassing with abstract superclasses by R. Sutcliffe 1998 09 25 *) IMPORT GeoFigures; FROM STextIO IMPORT WriteString, WriteLn, SkipLine; FROM SRealIO IMPORT ReadReal, WriteReal; FROM RealMath IMPORT pi; (* first subclass of GeoFigures *) TRACED CLASS Rectangle; INHERIT GeoFigures.GeoFigure; VAR length, width : REAL; OVERRIDE PROCEDURE Area () : REAL; BEGIN RETURN length * width; END Area; OVERRIDE PROCEDURE Perim () : REAL; BEGIN RETURN 2.0 * (length + width); END Perim; OVERRIDE PROCEDURE InitSize; BEGIN WriteString ("enter length ==> "); ReadReal (length); SkipLine; WriteString ("enter width ==> "); ReadReal (width); SkipLine; END InitSize; END Rectangle; (* now subclass this in turn, making more overrides *) TRACED CLASS Square; INHERIT Rectangle; OVERRIDE PROCEDURE Area () : REAL; BEGIN RETURN length * length; END Area; OVERRIDE PROCEDURE Perim () : REAL; BEGIN RETURN 4.0 * length; END Perim; OVERRIDE PROCEDURE InitSize; BEGIN WriteString ("enter side length ==> "); ReadReal (length); SkipLine; END InitSize; END Square; (* and, a completely different subclass of the original *) TRACED CLASS Circle; INHERIT GeoFigures.GeoFigure; VAR radius : REAL; OVERRIDE PROCEDURE Area () : REAL; BEGIN RETURN pi * radius * radius; END Area; OVERRIDE PROCEDURE Perim () : REAL; BEGIN RETURN 2.0 * pi * radius; END Perim; OVERRIDE PROCEDURE InitSize; BEGIN WriteString ("enter radius ==> "); ReadReal (radius); SkipLine; END InitSize; END Circle; VAR (* for main block to test this stuff *) rect : Rectangle; square : Square; circ : Circle; BEGIN (* main module *) CREATE (rect); rect.InitSize; WriteString ("The area is "); WriteReal (rect.Area(), 15); WriteLn; WriteString ("The perimeter is "); WriteReal (rect.Perim(), 15); WriteLn;WriteLn; CREATE (square); square.InitSize; WriteString ("The area is "); WriteReal (square.Area(), 15); WriteLn; WriteString ("The perimeter is "); WriteReal (square.Perim(), 15); WriteLn;WriteLn; CREATE (circ); circ.InitSize; WriteString ("The area is "); WriteReal (circ.Area(), 15); WriteLn; WriteString ("The perimeter is "); WriteReal (circ.Perim(), 15); WriteLn;WriteLn; END DemoGeos.
One run of this module is shown below, with the inputs in bold.
enter length ==> 2 enter width ==> 3 The area is 6.0000000000000 The perimeter is 10.000000000000 enter side length ==> 5 The area is 25.000000000000 The perimeter is 20.000000000000 enter radius ==> 1 The area is 3.1415925025940 The perimeter is 6.2831850051880
This is very simple, and very minimal, both in form and in content. Many more figures and their associated methods could be derived, and there are more interesting methods such as displaying the figure on the screen that could be written. Also, the verbal clues in the output need work; the student who is interested may clean this code up considerably.