Creating Objects Lesson 12 Contents

Think of a class as a "factory". When one designs a class one is designing the factory. The purpose of the factory is make objects. That is the sole purpose of a class. Classes are also used to create other (sub)classes. What is so special about that? With our factory, we can easily make as many objects as we want.

Let's say your factory was designed as:

:class TESTCLASS super{ object }

:m NUMBER: 888 ;m

;class
By executing the name of the class followed by any object name we create an object from your factory. Here we create two objects from your factory:

TESTCLASS ObjectOne
TESTCLASS ObjectTwo

What you did was create two instances of class TESTCLASS. Very efficient system.

But an object will only do something when it is sent a message. The message(s) were defined in the factory (or class). Your objects will respond to the NUMBER: message as follows:

number: ObjectOne
number: ObjectTwo 777 -

Two numbers should now be on the stack, each originating from a different object.

Now the above may not seem so fantastic, but it is just one of the excellent things that can be done with OOP. It is important to understand at the start and what is happening should be completely clear before moving on.

Think of the above as a sequence of three things that must happen in this order:

  1. Define the class with :class ;class
  2. Create an object by executing classname objectname
  3. Send a message to the object by executing message: objectname
Executing just the message alone will not do anything and will probably result in an error.

Method Inheritance
Subclasses inherit methods from their parent class. To illustrate, add this text to the previous example and compile/save with cmd-k:

:class TESTSUBCLASS super{ TESTCLASS }

;class
Now create an object and send a message:

TESTSUBCLASS ObjectTwo
number: ObjectOne number: ObjectTwo -

Even though the number 888 is not in TESTSUBCLASS, the method is inherited from its parent class. Both numbers (the same) are subtracted leaving 0 on the stack.

Having accomplished that, try executing the following (send the following messages to either of your objects):

.ID: ObjectOne
.CLASS: ObjectTwo

If you are wondering what happened, in QuickEdit try a command-click on the word " object ", which is the name of the superclass of your TESTCLASS. QE will open the definition of the class object where you will see the definitions of the messages that your class inherited. Don't worry if those definitions look complex. All you really need to know is what happens when you send that message to an object. That is part of the beauty of OOP. You don't really need to know "how" everything works. You just need to know what happens when a message is sent to an object.

Learn more about classes and objects on this page.

Classes are always created in the dictionary. When you are finished experimenting, you can deallocate a class or dictionary based object by saying:

forget ObjectOne
forget TESTCLASS

Creating a Window
Now we come to creating an object of class Rect and sending messages to that object so it can select the methods to execute. To create an object of class Rect, the syntax is simply the name of the class followed by the name you want to assign to the object.
For an object named "Box1" of class Rect, the statement would be:

Rect Box1

That's all there is to it! By creating this object, you have added a new Mops word, "Box1" to the dictionary in memory. You can visualize the object in memory to look like: CreateObject.png Zeros are placed in the instance variable cells when the object is created, and they are holding space for numbers whenever the object receives a message to put data there.

When you type a Mops message in a program, it has three parts to it: the parameters, selector, and receiver.

Parameters are the numbers to be passed to the operation. They are placed on the parameter stack just like parameters in Lesson 1. Not all messages have parameters, of course. Some operations don't require any numbers be passed to them.

The second part, the selector, is actually the name of the method containing the operation you want the object to perform. In other words, the object "selects" which method of its class is to be put to work; the object matches the message's selector with the method in the object's class (or up the superclass hierarchy if there is no match in the immediate class).

The last part of a message, the receiver, must be the name of an object. It is the "thing" on which you want to perform the operation specified by the selector. In the accountant metaphor, the receiver is the name of the accountant who is to "prepare the returns".

Since Box1 is an object of class Rectangle, you can send a message to it that selects one of the methods defined in class Rectangle. If you send the message:

300 20 400 100 put: Box1

you put the coordinates 300, 20 and 400,100 into the data cells reserved for TopLeft and BotRight in the Box1 object. After all, that's what the put: method in Box1's class does: it places two sets of two parameters into an object's data cells.

If, at some future time, you create a new object of class Rect, called "Box5", Box5's data cells would be empty at first. A separate put: message would have to be sent to Box5 to place Box5's coordinates in that object's data cells. This is how objects maintain private data.

To draw the objects on the screen, you need to send another message, one that calls upon the draw: method of class Rect.
The message would be:

draw: Box1

As you type or drag these commands into the window, watch closely when you press «enter» and you will see the square momentarily flash in the Mops window.

If you were defining class Rect from scratch, you could also define a new method that combines the functions of two methods into one. Then, a single message would take care of both the put: and draw: methods. For this to happen, you need a way for the new method to look up the methods in the same class. That's where a message receiver called "self" comes in handy.
With the new method (place:) the class looks like this:

:class RECT super{ object }

record
{	point	TopLeft
	point	BotRight
}

:m PUT: ( l t r b -- )	put: botRight   put: topLeft ;m

:m DRAW: ( -- )		^base FrameRect ;m

:m PLACE: ( l t r b -- )    ( draw at new coordinates )
	put: self draw: self ;m

;class

The place: method contains the messages, "put: self" and "draw: self". The put: self message is saying "Do to the current object everything that the put: method in this class does". The same goes for draw: self. If you had intended one of these messages to look up a method in Rect's superclass, the receiver would have been & "super", as in put: super.

Something important happens when you have the put: self message inside the place: method. The place: method now expects to find four integers passed along with any message bearing its selector, just like the actual put: method that executes the storage command requires four integers. Therefore, to both locate and draw Box1 on the screen, you would send the message:

12 10 100 50 place: Box1

If you want to try this, you'll need to have a window to display box1 in, as you did in the Intro. So first copy the above Rect definition to the Mops window (either by select&drag or by copy&paste). Then select the whole of the definition (by dragging with the mouse). Then hit the «enter» key. This will cause all the selected text to be executed. In this case, since the code is a class definition, the result of executing the code will simply be to define the class Rect. Nothing will seem to happen, but the definition for Rect will have been entered into Mops' dictionary.

Now type and execute this:

window ww
test: ww

Click back on the Mops window and move things around so you can see both the Mops window and ww, then type and execute

rect box1
set: ww 12 10 100 50 place: box1

and your Rect should appear in the window ww.

This is a bare-bones exercise, but normally you would save the text in a QuickEdit file, save it with cmd-k and that will load the file into the Mops window. If you want to do this now, open QuickEdit. In a new window type the class definition and the other lines of code above, or copy and paste it from here. When finished, select Save or Save As... from the File menu, and assign a short, recognizable name to the file, like "rr". You can actually use any text editor you want, and if you do, files must be saved as ASCII format. But QuickEdit is optimized for use with Mops.

Close the editor window to return to PowerMops. Load the file into PowerMops by choosing Load from the File menu and navigating to your file.

The advantage of using an editor rather than the Mops window directly is that you now have a file available for later use. But if you are just experimenting, it is quicker to use the Mops window, and you can still select the text and copy and paste it into a file in an editor's window at any time.

Summary
Before taking one more step, let's summarize.
Creating a Mops program entails the following steps: defining classes; creating objects that are instances of those classes; and then sending messages to those objects.
Building a hierarchy of classes starts with the broadest class and works toward the more specific, with subclasses inheriting the characteristics of their superclasses.

This diagram will help you visualize the structure of the program example detailed in this chapter. It graphically portrays the relationships between the classes and objects discussed above.

ObjectStructure.png Given this framework, when you issue the message 12 10 100 50 place: Box1, the parameters fill Box1's data cells held in reserve when Box1 was created.

The characteristics of the data had already been determined by the ivars TopLeft and BotRight; the characteristics of those ivars had been likewise determined by the ivars X and Y, which, in turn, had been defined by the methods of their defining class, class Int.

Therefore, you probably recognize that the relationships in Mops classes and objects are on multiple levels. On the one hand, you have the relationships between superclasses and subclasses. On the other hand, you have the relationships between ivars and their defining classes. Both relationships cascade through the hierarchy of a Mops program independently of each other. That will become even clearer as we make one further extension to the example above.

 

Previous Chapter Contents Next Chapter
This page online:  http://PowerMops.com/MopsManual/Lessons/Chapter12.html