As you know by now, Smalltalk is an Object Oriented programming language. In this chapter I will introduce you to two concepts that lie at the heart of this; classes and methods. We have already mentioned that all of Smalltalk's objects know what class they are and that it is this class that governs how they should behave. This behaviour is recorded in the class by a number of methods. A method contains the Smalltalk code that determines how a particular message should be executed.
This chapter introduces you to classes and methods and the Class Hierarchy Browser, which is the development tool that you use to manipulate them. To an extent, this chapter is largely theoretical, but hang in there since this is important stuff and you only need to wait a short while until the next chapter to get on with some practical Programming in Smalltalk.
Let's ask a few objects what class they are. Open a fresh workspace and evaluate the following expressions, displaying the results:
'This is a string'. #(7 'abc' 6.15) 4 @ 5.
Okay, so we've seen that the objects above are classified as a String, an Array and a Point respectively. Now the interesting thing here is that the classes String, Array and Point are also objects too (remember everything is an object in Smalltalk). The fact that class objects have names that begin with an upper case letter should, perhaps, indicate to you that these objects are held in global variables. There are around 600 classes in the basic Dolphin image so there are this number of different types of object, all with different behaviours. Naturally, this number will grow as you program new classes for yourself.
Every object is associated with a particular class. In Smalltalk programs we say that every object is an "instance" of its class. This means that 'hello' is an instance of String and 4 @ 5 is an instance of Point.
Question: if a class describes the behaviour of its objects and yet a class is also an object, what describes the behaviour of a class? What is a class object an instance of? The answer is a metaclass. I'm afraid that metaclasses are beyond the level of our current discussion but they are intriguing. Indeed, many computer scientists would consider them to be one of the most intriguing aspects of Smalltalk but, for more information, you'll have to research this for yourself!
So what do class objects actually do and what do they contain?
One thing classes do is to describe the layout of data in their instances. Most objects have to hold information about their state and they do this using instance variables. We'll learn more about instance variables in a moment but for now, suffice it to say that it is the responsibility of a class object to define the number and names of these variables in its instances. You can find out what instance variables are required by an object by sending the message #allInstVarNames to its class. Try:
Point allInstVarNames.
This answers an array of strings showing the instance variable names for all Point objects. As you might expect, a Point (which represents a point on a two dimensional Cartesian plane) has variable slots for its x and y co-ordinates.
Another thing that classes do is to hold onto pieces of Smalltalk code called methods that describe how instances respond to individual messages. You can query a class to determine what message selectors its instances will respond to. Try displaying the results of:
Point allSelectors. Point respondsTo: #<=. (4 @ 5) respondsTo: #<=.
Once you know that a class supports a particular message then you can also ask to see the code for the method that implements the message. Let's take a look at the Smalltalk code that handles the comparison of two points:
(Point compiledMethodAt: #<=) getSource.
The important thing to note here is not so much the code itself, but the fact that Smalltalk objects can be so introspective. You can send them messages to do many things, including asking them to describe how they actually work. This is another important aspect of Smalltalk and is often called reflection since objects have the ability to reflect upon their own behaviour.
If, every time you wanted to look at some code in your Smalltalk image, you had to evaluate an expression like the one above then things would soon get pretty tedious. Fortunately, there is a much easier way, and this is to use a tool called the Class Hierarchy Browser. To bring up such a browser choose Tools/Class Hierarchy Browser or click on the Browse Classes toolbar button in your workspace.
You can use this window to browse through all of the classes in your image. The top left pane allows you to select a particular class to browse. Smalltalk classes exist in a hierarchy, which will be explained in the next chapter in the section on Inheritance. For time being just be aware that the root of this hierarchy is a class called Object and that this is the class that is initially visible in the top left class hierarchy pane of the Class Browser.
The easiest way to find a class so that it can be browsed is to select the class hierarchy pane and type Ctrl+F. Try this and you should be prompted to enter the name of a class to search for. Type, Point, to locate this class and display it in the browser. The top right methods pane now contains a list of all the message selectors that Point objects can respond to. The lower pane currently displays the class definition, although there are also two other tabs available to allow it to show either method source code or a comment for the current class.
It is using this tool that you will perform most of your programming work with Dolphin Smalltalk. Not only can you browse through existing classes and methods in the system but you can also create new ones. We'll see how to do this in the subsequent chapter but for moment let's just a do some browsing.
I've already mentioned that methods are chunks of Smalltalk code that describe to an object how a particular message should be processed. Let's look at some of the methods for class Point. With this class selected in the browser, choose <= in the top right methods pane. The browser will bring up and display the Smalltalk source for the method that handles the #<= comparison message for points.
<=anArithmeticValue "Answer whether the receiver is neither below nor to the right of anArithmeticValue. A double dispatch of #< with the superclass implementation of #<= would not work here." | aPoint | aPoint := anArithmeticValue asPoint. ^x <= aPoint x and: [ y <= aPoint y ]
This method illustrates a few points that we haven't yet touched upon in the Smalltalk code you've written so far. Let's go through these one by one.
The first thing to note is that the method text is short. This is very often the case with Smalltalk code and you should find that, as a matter of good style, you'll end up writing a lot of rather short methods rather than a few long ones. This makes it very easy for the reader to assimilate how a method works. Also, by breaking down a program into many small chunks, it becomes more flexible. A method is more likely to be re-usable in future situations if it does not try to do too much at once. By re-using pre-written methods your programs will be smaller and easier to maintain.
The next important thing is the first line, which forms the method header. This defines the selector for the method (<=) and any parameters that the method takes. Since #<= is a binary message we expect it to take a single parameter which, in this case, we have called anArithmeticValue.
Tip: it is very common in Smalltalk to describe the method parameters using names like anArithmeticValue, aString, anInteger etc. This is not enforced; it is just a way of helping the reader determine what sort of parameter a particular method expects.
Once the parameters have been named at the beginning of the method they can be used like any variable in the body text. In fact, that's not quite true because you're not allowed to assign to them; they are read only. For this reason they are sometimes called pseudo-variables.
Some methods, but certainly not all, may require the definition of some temporary variable slots in which working results can be held during execution of the method. If these are needed then they are defined immediately after the method header and are enclosed between vertical bar (|) characters. This method defines one temporary variable called aPoint. Temporaries only exist while the method is executing and any objects that are installed in temporary slots are released when the method completes.
Tip: it is convention that all pseudo-and temporary variable names should begin with a lower case letter.
We have mentioned previously that all message sends answer a result. This must imply that all methods do so too. A result can be returned from a method by pre-fixing an expression with the ^ character. In this example we are answering the result of the following boolean expression:
x <= aPoint x and: [ y <= aPoint y ]
You can, in fact, return a result from anywhere within a method; you do not have to hold back until the last line. Also, you don't have to explicitly return anything at all. If you omit to include a return (^) expression then, by default, the method will answer the receiving object.
You can learn a lot about Smalltalk just by browsing, so let's take a look at some more methods of class Point. Select the method for #dotProduct:.
dotProduct: aPoint "Answer a Number that is the sum of the product of the x coordinates and the product of the y coordinates of the receiver and aPoint" ^(x * aPoint x) + (y * aPoint y)
This illustrates the definition of a method for a keyword message. In this case it is slightly simpler than the previous example because there is no need to declare any temporary variables. Notice that, in nearly all methods, it is conventional to immediately follow the method header with a comment that describes the purpose of the method. The comment should be indented by one tab space and followed by a single blank line before the body of the method code. Once again this is only convention but following this style will make the code you write easier to understand.
Tip: when describing a particular method to someone we often refer to it using a form like Point>>dotProduct:. This is quite important when you realise that the same message selector can be handled by many different classes.
Now select #x:y: in class Point (Point>>x:y:).
x:xCoord y: yCoord "Private - Set the x and y coordinates of the receiver. Primarily intended for instance creation. Answer the receiver." x := xCoord. y := yCoord
This shows the definition of a method with two parameters. Note also, that there is no explicit statement returning a value so this means that the method will answer the receiving point when it completes. Also interesting is the fact that, in the comment, the method is marked as being "Private". Some methods are written to be of use only by the original programmer of the class itself and are not expected to be used generally by others. These methods are said to be private to the class and are indicated as such in their method comments.
Tip: unlike many other programming languages, Smalltalk does not enforce this idea of method privacy. It is perfectly possible for you to call any private method at any time. What the method author has indicated is that this may not be a good idea, either because the method might have unusual side-effects, or because the method is not guaranteed to be present in future implementations of the class.
I've already mentioned that objects use instance variables to store their data and that it is an object's class that has knowledge of these instance variables. If you go back to the class definition for Point (click on the Class Definition tab in the browser) you'll see, amongst other things, that it defines the instance variable names for its instances.
ArithmeticValue subclass: #Point instanceVariableNames: 'x y ' classVariableNames: '' poolDictionaries: ''
We'll look at other aspects of this definition in the next chapter but, for the moment, let's just concentrate on how the instance variable names are specified. You can see that this is done using a string containing a list of the variable names separated with spaces. As we saw in a previous example points have two instance variables called x and y.
Like all Smalltalk variables, named instance variables are just slots where other objects can be stored. They are not "typed" and therefore not constrained to holding particular classes of object. They are also only directly accessible from methods within the class itself (or its subclasses, which we'll talk about later).
Tip: this is a very important precept in Object Oriented Programming; that the internal format of the data of an object must remain hidden at all costs from the users of that object. When you want to know something about an object you send it messages. This allows the implementer of the object the flexibility to change its internal representation at some point in the future and, providing that the set of messages it responds to remains the same, nothing will be broken by such a change.
Since instance variables are not available to clients (users) of an object we needed to provide some means by which their data can be accessed when it is appropriate. We do this by adding methods to the class that are used to get and set the values of the instance variables. Such methods are generally known as accessor methods. Take a look at the following methods in the Point class:
x "Answer the receiver's x coordinate" ^x x: aNumber "Set the receiver's x coordinate" x := aNumber
These two methods are used to get and set the value of the receiver's x instance variable. You can also use accessor methods that set multiple instance variables at once or possibly calculate the value of an aspect of the object based on its instance variable contents. For example, take the following two methods:
x: xCoord y: yCoord "Private - Set the x and y coordinates of the receiver. Primarily intended for instance creation. Answer the receiver." x := xCoord. y := yCoord r "Answer the receiver's radius (magnitude) in a polar coordinate system." ^(self dotProduct: self) sqrt
Sometimes a class may contain many methods and to see them all in the Class Browser simultaneously might prove to be confusing. To help with this, Dolphin provides a system whereby each method can be categorised to associate it with other methods having a similar purpose. With the Browser still looking at Point take a look at the top centre categories pane. You'll see that this displays a list of the categories pertaining to the methods held by this class. Initially, All is selected and this means that all methods, no matter what category they belong to, are displayed in the methods list pane to the right in the Browser.
Now click on the accessing category and see how the Browser reformats to only display methods that are used to access a Point object's state. We saw most of these when discussing Accessor Methods a few moments ago. There are many different method categories in the Dolphin image and, as you continue to browse the system, you'll rapidly become familiar with them.
If you want to return the browser to displaying all methods for a particular class simply ensure that All is selected once again in the list of categories.
For some classes of object, normally those that hold collections of other objects, it is not convenient to simply use named instance variable slots for the object's storage. Hopefully you can see why, since you might need hundreds of separately named variables and even different numbers of variables depending on how many objects you wanted to hold on to. For this reason, certain classes are allowed to have indexed instance variables associated with them.
Each of the instance variable slots is accessible using an integer index. This makes an object that uses indexed variables very much like an array in most other programming languages. The index of the first slot is one, so we generally say that Smalltalk indexes are one-based (rather than zero-based).
The number of available indexed variables can be altered dynamically by sending the object a #resize: message with an integer parameter containing the number of slots required. This makes it very easy to implement classes that can hold collections of other objects. Smalltalk comes complete with a very powerful set of collection classes and we will discuss these in a later chapter on Collections. For the moment, let's just take a look at the class definition of a common collection class in the base image. Using the Class Hierarchy Browser, find the class called Array (remember you need to select the top left-hand pane and type Ctrl+F). You should see the following class definition displayed:
ArrayedCollection variableSubclass: #Array instanceVariableNames: '' classVariableNames: '' poolDictionaries: ''
The class is being defined using a single message with a rather lengthy selector, #variableSubclass:instanceVariableNames:classVariableNames:poolDictionaries:. This message is being sent to the class called ArrayedCollection on which the new class Array is to be based. Note the difference between this definition and the one for Point. There are no named instance variables required here and, in addition, there is the use of variableSubclass: as part of the message selector rather than just plain old subclass:. Now, without going into too much detail at this stage, you just need to take away from this that the Array class is being defined as having a variable number of indexed instance variable slots rather than a fixed number of named instance variables.
So far, you've seen how a Smalltalk class is an object that specifies how instances of that class behave. It does this by holding on to all the methods that implement the messages the instance can respond to, and by keeping information about the layout of the instance's data (the instance variables etc.). However, a class object also has an additional useful function; it can act as a "factory" for manufacturing its instances. We've seen this before in Chapter 2:
rect := Rectangle origin: 0@0 extent: 10@10.
Here we are sending the message #origin:#extent: to the Rectangle class with the expectation that it will answer a new instance of a rectangle. In this way the class is acting like a factory object capable of a manufacturing instances to our specifications and returning them for us to use in any way we see fit. The most common instance creation method is #new which can usually be sent to a class to create a new instance when no additional parameters are required to define it. For example, when we create new shapes to add to a Playground we use something like:
playground add: Triangle new.
By now you should be pretty familiar with the basics of using the Class Hierarchy Browser. If you want to learn more about its use then take a look at the online help, which can be accessed by pressing F1 with the browser as the active window.
In this chapter you have been introduced to the following fundamental aspects of object-oriented programming:
Click here to move on to the next chapter or here to go back to the previous chapter.