Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Structured Programming


JAN88: STRUCTURED PROGRAMMING

Object-Oriented Programming in Pascal

Object-oriented programming has become quite popular in the last few years. One aspect of its growing acceptance is the increasing number of structured and AI language implementations that support this new paradigm--for example, there are object-oriented implementations in C, Pascal, LISP, Lego, and Forth. In recognition of the magazine's focus this month on the 68000 line, I'll look at a new object-oriented Pascal available for the Macintosh.

The strength and attraction of object-oriented programming is that it offers you both a new concept (or technique, if you like) for modular coding and a method for efficiently describing a hierarchy of data structures (because redundant components need not be redeclared).

Objects in Pascal can be regarded as highly evolved record structures. Objects declare instance variables (similar to fields of Pascal records) and instance methods, or methods for short. These methods are routines that manipulate the objects. Consider the following object-type declaration:

TYPE TRectangle = OBJECT
{instance variables}
     Length, Width : real;
{instance methods}
     FUNCTION Area : real;
     FUNCTION Circumf : real;
     FUNCTION Diagonal : real; END;

which declares a class of objects named TRectangle that contains two instance variables--namely, Length and Width. Three methods are supplied to calculate the rectangle's area, circumference, and length of diagonal. The declared methods (all of which are functions in the preceding example) are implicitly FORWARD declarations of the routines' headings. The actual code is placed after all the other declarations.

To use the TRectangle object type, I must declare a variable and then allocate its dynamic memory using the predefined NEW() procedure. Accessing the fields and routines employs the familiar dot notation used with Pascal records. Consider the following code fragment:

VAR Rect : TRectangle;
A : real;

BEGIN
      NEW(Rect);
      Rect.Length := 8.0;
      Rect.Width := 5.0;
      A := RectArea;

In this code the name of the object is used in conjunction with all the object's instance variables and methods.

A rectangular parallelepiped is a 3-D rectangle that can be regarded as extending the 2-D "parent" shape into a third dimension. This means the parallelepiped shares the same features as the 2-D rectangle and adds a few new ones. To enable the parallelepiped objects to inherit the features of the 2-D rectangle, I declare the following:.

TYPE TSolid = OBJECT (TRectangle)
     height : real;
FUNCTION Volume : real;
FUNCTION Space__Diag : real; END;

This declaration states that the object type TSolid is a child of the object type TRectangle. As a child object, it is able to inherit all the instance variables and methods of its parent object:

VAR Solid : TSolid;
A, V : real;
BEGIN
      NEW (Solid);
      Solid.Length := 8.0;
      Solid.Width := 5.0;
      Solid.Height := 7.0;
      A := SolidArea;
      V := Solid.Volume;

Notice how the variable Solid (of type TSolid) is able to access the instance variables and methods of the TRectangle object type directly. Unlike nested record structures in Pascal, nested objects can make direct reference to the ancestor's variables and methods.

In the preceding discussion, I pointed out that the class of objects TSolid is able to inherit components from its parent object. Object-oriented Pascal provides a convenient mechanism to enable a new subclass of objects to define its own version of certain inherited methods. Such methods offer either additional or alternate ways of manipulating an object.

Consider, for example, the case of making an alternate definition of the object type TSolid. I want to rename the method Space__Diag as Diagonal. Because the Diagonal name is also used by a method in object TRectangle, I must resolve the conflict I have just created. The keyword OVERRIDE is used to override inherited versions, so my new alternate declaration for the TSolid object type is:

TYPE TSolid = OBJECT(TRectangle)
      height : real;
      FUNCTION Volume : real;
      FUNCTION Diagonal : real;
      OVERRIDE; END;

The OVERRIDE keyword is placed after the sought method, followed by a semicolon. To revert to the method inherited from a parent object type, instead of the overridden method, the keyword INHERITED must be placed before the method in question.

In certain cases it may be necessary to make a reference to an object associated with a method. Object types are similar to pointers, and hence they must be dynamically allocated. A problem arises because methods cannot allocate the objects they manipulate. To solve this dilemma, object-oriented Pascal provides a special identifier, SELF. Using SELF, you can make references to the object that is yet to be created--for example, the code for method Volume can be written as:

FUNCTION Volume : real;
BEGIN
     Volume := SELF.Length *
           SELF.Width *
           SELF.Height; END;

TML Pascal for the Mac, the only object-oriented Pascal implementation I've seen, allows you to omit the SELF reference.

Example 1: Declaration of object types for various kind of menus

CONST MAX_MENU = 20

TYPE STRING80 = STRING[80];
     String_Array = ARRAY [0. .MAX+MENU] OF STRING80;
     Menu_Range = 0..MAX_MENU;

TMenu = OBJECT
     ( declare instance variables )
     Menu_Options : String_Array;
     Menu_Choice : Menu_Range;
END;

TItem_Menu = OBJECT(TMenu)
     PROCEDURE Display_Menu;
     FUNCTION Get_Choice : Menu_Range;
END;

TControl_Item_Menu = OBJECT (TMenu);
     Current_Level : Menu_Range;
     PROCEDURE Display_Menu;
     FUNCTION Get_Choice : Menu_Range;
END;

TMain_Pull_Down = OBJECT (TMenu)
     PROCEDURE Display_Menu;
     FUNCTION Get_Choice  Menu_Range;
END;

TPull_Down = OBJECT (TMenu)
     Hot_Key_Char : ARRAY [0.MAX_MENU] OF CHAR;
     Location : ARRAY [0.MAX_MENU] OF INTEGER;
     Attribure : ARRAY [0.MAX_MENU] OF BYTE;
     Active : ARRAY [0.MAX_MENU] OF BOOLEAN;
     PROCEDURE Display_Menu;
     FUNCTION Get_Choice : Menu_Range:
END;

A Sample Program

Object-oriented programming can be applied across the board, including with basic data structures. Listing One, page 66, shows a complete TML Pascal program that implements simple objects to represent various types of simple numeric stacks.

The first object defined, Tstack, contains instance variables and methods used by all the other object types. In this case, the instance variables include the stack height and a Boolean flag used to indicate errors (in the program I use it to indicate that an attempt was made to pop an empty stack; you can also use the flag to indicate an attempt to divide by 0). The methods associated with TStack objects initialize, increment, and decrement the stack height.

The second object type, TRealStack, is a child of object TStack. It defines the stack as a four-element, real-type array. The methods associated with this object type are Push, Pop, and Add. (I've omitted other math-related routines to keep the listing short.) The third type, THPStack, is a child of the object type TRealStack. This new object type defines the instance variable LastX to store the value of the first array element when a stack addition is performed. This also dictates that an overridden Add method be defined.

Finally, the fourth object type, TIntStack, is a descendant of object TStack that implements an integertype version of object TRealStack. The main code portion exercises the methods defined with the objects.

Menus As Objects

Objects find applications in other data structures, such as lists, arrays, and matrices. They can also be applied to data structures representing screens, windows, and menus. Example 1, page 109, shows the declaration for object types representing different kinds of menus.

The TMenu object type defines instance variables that perform the following:

  • Tackle the text for menu options.
  • Store the number of actual options available.
  • Store the number of the option selected (the 0th option is used for exiting from the menu).
TItem__menu is a menu object type that builds on TMenu simply by adding two methods: one to display the menu and another to return the selected choice. The object type TControl__Item__Menu is a modified version of TItem__Menu that allows you to implement progressive menus that gradually reveal more options. These menus display an itemized menu that contains only the options available to you at the current level.

The TMain__Pull__Down object type uses the same instance variables of object type TMenu; however, it implements its own version of the methods to display the main menu of a pull-down menu system. The individual options of a pull-down menu are defined by the object type TPull__Down. This type declares an additional number of instance variables that are arrays that serve to:

  • Define the hot-key characters.
  • Locate where the hot-key characters are displayed and their accompanying display attributes.
  • Define Boolean flags to indicate whether an option is active or passive.

Inheritance Problems

Object-oriented Pascal limits objects to single inheritance: one object type has at most one parent object type. Other languages and implementations, such as Smalltalk, Object Logo, and ExperCommon LISP, support multiple inheritance. I'd like to see multiple inheritance implemented in future versions of object-oriented Pascal because it offers the flexibility and power to model real-world objects realistically. This new level of sophistication generates new problems, though.

Among the first problems to resolve is the question of inheriting instance variables and methods that are present in the ancestor objects. The solution involves assigning influence levels, or priorities, to ancestor objects and providing the ability to override them. I suggest some alternate rules for resolving inheritance conflicts by assigning influence levels as follows:

    1. The first ancestor object mentioned is the dominant parent, and all other ancestor objects are of equal importance. In the following example:

TYPE    Car = OBJECT (Make, Engine, Body)

the ancestor object Make has the greater influence concerning conflicting instance variables and methods and the objects Engine and Body have the same influence. This syntax assumes that there are no conflicts pending between the last two object types.

    2. The order of listing the ancestor objects indicates their influence levels. The following object heading declaration illustrates this syntax:

TYPE    Car = OBJECT (Make,  Engine, Body)

Here, the Make and Body object types have the strongest and weakest influences, respectively.

    3. The list of ancestor object types is partitioned into two sublists using a special character--say, the bar symbol. The first sublist has its ancestor object types listed in decreasing influence; the second has the rest of the ancestor object types on equal footing. Consider the heading:

TYPE    Car = OBJECT (Make, Engine | Body, Doors)

The first sublist contains object types Make and Engine, and the second sublist contains Body and Doors. The object type Make has the dominant influence, followed by Engine. The Body and Doors object types have an equal influence, which is weaker than that of the first two. This syntax also assumes that there are no conflicts pending between the last two object types.

    4. Influence levels are explicitly assigned to all ancestor object types using the syntax <object type> = <unsigned integer constant>. The integers used need not be in any particular sequence, as shown in the following example:

TYPE    Car = OBJECT (Make = 100, Engine = 1, Body = 40)

This object type declaration indicates that the object type Make has the highest influence (by virtue of the number assigned to it and not its position in the list of object types). By contrast, the Engine object type is the least influential.

The first and third alternatives contain a common weak point: they may be unable to resolve all the inheritance conflicts. The second and fourth suggested syntaxes are conflict-proof. Nevertheless, you need a mechanism to arbitrate or override inheritance rules explicitly. I suggest using the keyword FROM to indicate the parent object type supplying the particular attribute, as shown by the following general syntax:

<variable> : data <type> FROM <object type> <method> FROM <object type>

Tracking down inherited variables and methods in multiple inheritance is much more complex than in single inheritance. A single link in the latter is replaced by a complex search graph in multiple inheritance. The increased real time for processing multiple inheritance could be compensated for, however, by using fast CPUs and electronic disks, which would keep the overall compilation time relatively short.

A Final Note

Writing this column for the last few years has been fun, but it has also required a great deal of writing time. Other obligations, including the job of editor of M&T's Turbo Tech Report, have made it necessary for me to pass this column's duties on to another member of the DDJ family. Although I will continue to write an occasional article for DDJ, this month marks my last Structured Programming column. Thank you for your support, and take care of yourselves.

Bibliography

Cox, Brad J. Object-Oriented Programming: An Evolutionary Approach. Reading, Mass.: Addison-Wesley, 1987.

Schmuker, Kurt J. Object-Oriented Programming for the Macintosh. Hasbrouck Heights, N.J.: Hayden, 1986.

[LISTING One. Example for declaring various objects that are variations of stack structures]

<a name="0043_000b">

PROGRAM Test_Objects(input,output);

{ Simple example for objects using TML Pascal running on a Mac Plus }

TYPE RealStackReg = ARRAY [1..4] OF real;
     IntStackReg  = ARRAY [1..4] OF integer;

     { define common stack-related variables and routines }
     TStack = OBJECT
        height : integer;
   ErrorFlag : boolean;
        PROCEDURE TStack.IStack;
   PROCEDURE TStack.Inc(VAR i : integer);
   PROCEDURE TStack.Dec(VAR i : integer);
     END;

     { define a 4-register real-typed stack }
     TRealStack = OBJECT(TStack)
        Regs : RealStackReg;
   PROCEDURE Push(Item : real);
   FUNCTION Pop : real;
     PROCEDURE Add;
     END;

     { define a 4-register real-typed stack with automatic LastX register }
     THPStack = OBJECT(TRealStack)
        LastX : real;
   PROCEDURE Add; OVERRIDE;
     END;

     { define a 4-register integer-typed stack }
     TIntStack = OBJECT(TStack)
        Regs : IntStackReg;
   PROCEDURE Push(Item : integer);
   FUNCTION Pop : integer;
   PROCEDURE Add;
     END;


PROCEDURE TStack.IStack;
{ Initialize TStack objects by setting the Stack height to zero }
BEGIN
  height := 0
END; { Stack.IStack }


PROCEDURE TStack.Inc(VAR i : integer);

BEGIN
  i := i + 1;
END;

PROCEDURE TStack.Dec(VAR i : integer);

BEGIN
  i := i - 1;
END;


PROCEDURE TRealStack.Push(Item : real);

VAR i : integer;

BEGIN
   Inc(height);
   FOR i := 4 DOWNTO 2 DO
      Regs[i] := Regs[i-1];

   Regs[1] := Item;
END; { RealStack.Push }


FUNCTION TRealStack.Pop : real;

VAR i : integer;

BEGIN
   IF height > 0 THEN BEGIN
      Pop := Regs[1];
      FOR i := 1 TO 3 DO
         Regs[i] := Regs[i+1];
      Dec(height);
      ErrorFlag := FALSE;
   END
   ELSE BEGIN
      Pop := 1.0E+30;
      ErrorFlag := TRUE
   END;

END; { TRealStack.Pop }



PROCEDURE TRealStack.Add;

VAR i : integer;

BEGIN
  Regs[1] := Regs[1] + Regs[2];
  FOR i := 2 TO 3 DO
    Regs[i] := Regs[i+1];
END; { TRealStack.Add }

PROCEDURE THPStack.Add;

VAR i : integer;

BEGIN
  LastX := Regs[1];
  Regs[1] := Regs[1] + Regs[2];
  FOR i := 2 TO 3 DO
    Regs[i] := Regs[i+1];
END;{ THPStack.Add }

PROCEDURE TIntStack.Push(Item : integer);

VAR i : integer;

BEGIN
   Inc(height);
   FOR i := 4 DOWNTO 2 DO
      Regs[i] := Regs[i-1];

   Regs[1] := Item;
END; { TIntStack.Push }


FUNCTION TIntStack.Pop : integer;

VAR i : integer;

BEGIN
   IF height > 0 THEN BEGIN
      Pop := Regs[1];
      FOR i := 1 TO 3 DO
         Regs[i] := Regs[i+1];
      Dec(height);
      ErrorFlag := FALSE
   END
   ELSE BEGIN
     Pop := -32767;
     ErrorFlag := TRUE
   END;
END; { TIntStack.Pop }



PROCEDURE TIntStack.Add;

VAR i : integer;

BEGIN
  Regs[1] := Regs[1] + Regs[2];
  FOR i := 2 TO 3 DO
    Regs[i] := Regs[i+1];
END; { TIntStack.Add }

{ ---------------- declarations of variables ------------------- }

VAR RealStack : TRealStack;
    IntStack : TIntStack;
    { HPStack : THPStack; }
    X : real;
    I : integer;
    ch : char;

BEGIN
  NEW(RealStack);
  NEW(IntStack);
  NEW(HPStack);
  { exercise real-type stack }
  RealStack.IStack;
  RealStack.Push(1.0);
  RealStack.Push(2.0);
  RealStack.Push(3.0);
  RealStack.Push(4.0);
  RealStack.Add;
  X := RealStack.Pop;
  WRITELN('X = ',X); WRITELN;
  { use HPStack }
  HPStack.IStack;
  HPStack.Push(0.0);
  HPStack.Push(0.0);
  HPStack.Push(3.0);
  HPStack.Push(4.0);
  INHERITED HPStack.Add;
  X := HPStack.Pop;
  WRITELN('X = ',X);
  WRITELN('LastX = ',HPStack.LastX);
  WRITELN;
  { exercise integer-type stack }
  IntStack.IStack;
  IntStack.Push(1);
  IntStack.Push(2);
  IntStack.Push(3);
  IntStack.Push(4);
  IntStack.Add;
  I := IntStack.Pop;
  WRITELN('I = ',I); WRITELN;
  readln(ch);
END.



CONST MAX_MENU = 20;

TYPE STRING80 = STRING[80];
     String_Array = ARRAY [0..MAX+MENU] OF STRING80;
     Menu_Range = 0..MAX_MENU;

TMenu = OBJECT
     { declare instance variables }
     Menu_Options : String_Array;
     Num_Options,
     Menu_Choice : Menu_Range;
END;

TItem_Menu = OBJECT(TMenu)
    PROCEDURE Display_Menu;
    FUNCTION Get_Choice : Menu_Range;
END;

TControl_Item_Menu = OBJECT(TMenu);
    Current_Level : Menu_Range;
    PROCEDURE Display_Menu;
    FUNCTION Get_Choice : Menu_Range;
END;

TMain_Pull_Down = OBJECT(TMenu)
    PROCEDURE Display_Menu;
    FUNCTION Get_Choice : Menu_Range;
END;

TPull_Down = OBJECT(TMenu)
    Hot_Key_Char : ARRAY [0.MAX_MENU] OF CHAR;
    Location : ARRAY [0.MAX_MENU] OF INTEGER;
    Attribute : ARRAY [0.MAX_MENU] OF BYTE;
    Active : ARRAY [0.MAX_MENU] OF BOOLEAN;
    PROCEDURE Display_Menu;
    FUNCTION Get_Choice : Menu_Range;
END;



Example 1: Declaration of object types for various kinds of menus












Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.