Mapping Class-Object Relationships

An object-oriented language like Mops builds programs around the same kinds of relationships as portrayed in the accountant analogy from the preceding lesson. Class definitions play a central role in the structure of a program. As such, the most important early step in planning a Mops program is to visualize what the main objects (the Actors) in your program will be doing. Because Mac OS is capable of recreating on-screen metaphors for so many different real-world objects; a bank book, an artist's canvas, a calendar, it is best to devise classes of Mops objects that bear a behavioral resemblance to the real-world items. Once you've determined what the program's classes will be, it's time to start writing the program by defining those classes with methods. Then create objects of those classes. Finally, write the messages to those objects that set the program in motion. Let's take the first steps in applying class-object relationships to a Mops program by defining a class that is capable of drawing rectangle objects on the screen. At the same time, you'll also be introduced to the way Mops programs really look. Pay particular attention to the physical structure of program listings, indentions, spacings, capitalizations, and the like. While Mops is pretty loose about how you can make your programs look, the ways prescribed hereafter will help you read printouts of your code for debugging and enhancement. Also consult Chapters 3, 5, and 6 of the Reference for in-depth discussions of this and related topics.

Defining a Class

As you may have noticed in the accountant class metaphor, each class was defined by what amounts to a series of behavioral rules or procedures that are to be followed whenever an object of that class is called into action. Defining a class then, entails establishing those rules and procedures: the methods.

Most classes also have information (data) associated with an object of the class. For example, the class of Family Accountants can dictate that every accountant of its class should be paid for his work. Every family accountant (John and Percival, for instance) carries with him a figure for his hourly rate. The class definition merely states Thou shalt have an hourly rate. When the objects are created, the rate is plugged into that variable. Importantly, John and Percival can have entirely different hourly rates, because they hoard their own data to the exclusion of other objects in the same class. One of their methods would retrieve the rate, multiply it by the number of hours spent on your taxes, and send you the bill.

Let's see what it's like to build a Mops class called Rect, which will define all the procedures for creating rectangle objects.

In the Carbon environment, a rectangle is defined by two points on the screen: the locations of the top left and bottom right corners of the rectangle. In other words, for every instance of a rectangle on the screen, an object of class Rect will need numbers to fill in these two variables. These variables, then, are called instance variables (ivars for short). They are the holding places in an object's definition for the requisite data (the two points) required before a rectangle can be drawn.

The class definition up to this point looks like this:

:class  RECT  super{ object }
point TopLeft
point BotRight

Notice several things. First of all, the beginning of a class definition is :class (pronounced "colon class") with no space between the colon and the word "class". There is at least one space or a tab between :class and the name of the class. We have put RECT in capitals to make it stand out, since this is where it is defined. However, this is not necessary, since upper- and lower-case are treated the same by Mops. You can use whatever style of formatting you prefer.

On the same line as the name of the class is a reference to the superclass from which our rectangle class is derived. This reference takes the form of the word super{ (no space between "super" and the left brace), then the name of the superclass, then a }. These three items are separated by spaces or tabs, as for all Mops words. We will see later that it is possible for a class to have more than one superclass, known as multiple inheritance. We won't go into the details of this now, except to say that if there is more than one superclass, these are put one after the other before the }, again separated by spaces or tabs.

Although in this example the superclass name is Object, this should not be confused with the general use of the word object in the Mops system, where it refers to all objects generally. In this one special case Object is a class. This may seem a little confusing, but it is actually because we do use the word "object" in a general way, that we have named this special class Object. This is because all classes in Mops can trace their inheritance to class Object. Thus, class Object defines the behavior appropriate to all Mops objects. This is why the name "Object" is appropriate for this class.

So by its inheritance, class Rect has at its disposal all the methods defined in class Object. If you are interested in what methods are defined for class Object, you could check the source code listing (located in 'PPC source' folder as the source file "qpClass").

The instance variables tell Mops to reserve memory space in the data area of any object created from this class. The amount of space to be reserved is determined by the characteristics of the instance variables, which are themselves defined by other classes. Here, the instance variables (ivars) are named TopLeft and BotRight, both belonging to the class Point. It would not be possible to create ivars TopLeft and BotRight in class Rect if class Point had not been previously defined. Fortunately, class Point is one of Mops' many predefined classes.

Note: For procedural language buffs, a key to understanding the object orientation of Mops is that as you follow the threads through the dictionary in the next few steps, you are not watching straight execution steps. Rather, you are building a framework that will reside in memory as a kind of potential energy that is released only when a message is sent sometime later in the program.

To understand what the rules and procedures are for the Point objects (TopLeft and BotRight) created inside class Rect, you can look up the Mops source code for the Point class (located in the "zQD" source file in the 'Toolbox classes' folder). The class definition should look something like this:

:class  POINT  super{ object }
record
{ int Y \ Vertical coordinate
int X \ Horizontal coordinate
}

...

;class

We'll explain all of this shortly, but the main thing to notice first of all is that this class, itself, uses two more ivars, X and Y, of class Int (integer). They specify the data area inside any object of class Point. In other words, any object created from class Point will need two integers to fill the cells reserved for data. The Point class was designed in this way so that two values, representing a coordinate point, would be conveniently coupled together whenever a Point object (an instance) was created.

Notice too, that we've started adding plain English remarks about the code as a way of documenting the program. There are three ways of specifying comments in Mops:

( this kind of comment continues to a )

\ This is another comment, which extends to the end of the line

(* This kind of comment
can go over several lines,
(* and can be nested *)
*)

Note that (, \\, (* and *) are Mops words, and so must be followed by a white space character. Thus if you had

(this isn't a comment)

Mops would try to recognize the word (this, and wouldn't treat the rest of the line as a comment. You'd probably get an undefined word error message.

We'll come back to the rest of the statements in the Point class in a moment. First, we must search once more, but this time for the class definition of class Int, because the data of class Point consists of ivars Y and X that have the characteristics of class Int. Class Int is defined in the file "pStruct" in 'PPC source' folder. The search reveals:

:class INT     super{ object }

2 bytes data

:m PUT: inline{ ^base w!} ;m
...

;class

Class Int is another one of Mops' predefined classes. It states, first of all, that its superclass, like many in Mops, is class Object. Next, it states that two bytes (16 bits) of data are set aside for each value whenever an integer object is created. The third line is a method of this class (preceded by :m and ended by ;m). These message inside this method definition stores an integer in a special area of memory. (Don't worry about details of this method definition for now.)

Going back to the definition of class Point, the ninth method:

:m PUT:                put: Y  put: X   ;m

is a single instruction for Mops to store both the X and Y coordinates in memory. Therefore, every time one of the ivars (TopLeft or BotRight of our rectangle class) is given two numbers for an X,Y coordinate, the entire coordinate is stored by one PUT: message. In other words, a PUT: message to a Point object requires two numbers (X and Y) on the stack.

Next in the class Rect definition come two methods:

:class  RECT  super{ object }
point TopLeft
point BotRight

:m PUT: put: BotRight put: TopLeft ;m ( l t r b -- )

:m DRAW: ^base FrameRect ;m

;class

As detailed in the stack notation, the first method, PUT:, requires four integers on the stack (here signified by the letters 'l', 't', 'r', and 'b') before an object executes it. The first two integers (the ones on the top of the stack) are put into the object's BotRight reserved cells as soon as the 'put: BotRight' message finds the definition of the PUT: method in BotRight's class, which is class Point.

The second two integers are placed in the object's TopLeft cells as the result of the 'put: TopLeft' message in this PUT: method. In other words, when an object of class Rect receives a message consisting of the PUT: selector, the object searches its own class for the corresponding methods definition. The method sends messages of its own to objects of other classes, and so on back through a chain of classes and objects until a method is reached that is defined purely in Mops words (as in the PUT: method in class Int). All the actions taken by this series of messages affect only the private data of the Rect object that received the message.

The second method in our rectangle class, DRAW:, calls a Carbon framework routine named FrameRect (part of the QuickDraw graphics library), to draw the rectangle according to coordinates currently in the data cells of the object being drawn. The data, of course, must be in the proper order that FrameRect expects. FrameRect and most other Carbon framework calls seek the address of an object's data. This is obtained by the word \^BASE in the DRAW: method. This address is then passed to the Carbon framework call.

What we have so far, however, won't work properly when we call the DRAW: method. This is because by declaring TopLeft and BotRight as we did, we have made them proper Mops objects. The problem with this is that Mops objects have some extra information at the start of the object's location in memory, which Mops uses to keep track of certain things including the class of the object. However, the Carbon framework doesn't know anything about Mops objects, and just expects 2 bytes each for TopLeft and BotRight, with no extra bytes present. Accordingly we have to have a way of omitting this extra information, and we do this with the 'record{ ... }' syntax, as follows:

:class  RECT  super{ object }
record
{ point TopLeft
point BotRight
}

:m PUT: put: BotRight put: TopLeft ;m ( l t r b -- )

:m DRAW: ^base FrameRect ;m

;class

Note: You can either use either 'record{ ... }' or 'record <var><optional name></var> { ... }'.

Any ivars declared as part of a record won't carry any extra information. This will limit some of the things you can do with these particular ivars (as you might expect), since Mops doesn't have the extra information available. But as we'll see, this isn't a very serious restriction.

Finally, be sure to end the class definition using ;class (pronounced "semicolon class").

One last thing: you can format your class definitions (and all of your code for that matter) however you like, as long as at least one space or tab separates Mops words. The formatting we use here in the Manual and in the Mops source code is considered quite readable by most people, so we recommend something like it. Plenty of white space and comments are always a good idea as it will greatly help anybody else who has to read your code to understand it (and you yourself for that matter, when in a few weeks time when it's no longer fresh in your mind). But in the end, the choice is up to you.