The message "self spend: amount" resends the message "spend:" to our own object. This causes its method to be looked up by Smalltalk. The method is then found in our parent class, Account, and our balance is then updated to reflect our spending.
You can try the following examples:
Smalltalk at: #c put: (Checking new) !
c printNl !
c deposit: 250 !
c printNl !
c newChecks: 100 count: 50 !
c printNl !
(c writeCheck: 32) printNl !
c printNl !
For amusement, you might want to add a printOn: message to the checking class so you can see the checking-specific information.
In this chapter, you have seen how to create subclasses of your own classes. You have added new methods, and inherited methods from the parent classes. These techniques provide the majority of the structure for building solutions to problems. In the following chapters we will be filling in details on further language mechanisms and types, and providing details on how to debug software written in Smalltalk.
-17-
6. Code blocks
The Account/Saving/Checking example from the last chapter has several deficiencies. It has no record of the checks and their values. Worse, it allows you to write a check when there are no more checks—the Integer value for the number of checks will just calmly go negative! To fix these problems we will need to introduce more sophisticated control structures.
6.1. Conditions and decision making
Let’s first add some code to keep you from writing too many checks. We will simply update our current method for the Checking class; if you have entered the methods from the previous chapters, the old definition will be overridden by this new one.
!Checking methodsFor: ’spending’!
writeCheck: amount
| num |
(checksleft < 1)
ifTrue: [ ˆself error: ’Out of checks’ ].
num := checknum.
checknum := checknum + 1.
checksleft := checksleft - 1.
self spend: amount
ˆ num
!!
The two new lines are:
(checksleft < 1)
ifTrue: [ ˆself error: ’Out of checks’ ].
At first glance, this appears to be a completely new structure. Look again! The only new construct is the square brackets.
The first line is a simple boolean expression. "checksleft" is our integer, as initialized by our Checking class. It is sent the message "<", and the argument 1. The current number bound to "checksleft" compares itself against 1, and returns a boolean object telling whether it is less than 1.
Now this boolean—being either true or false—is sent the message "IfTrue:", with an argument which is called a code block. A code block is an object, just like any other. But instead of holding a number, or a Set, it holds executable statements.
So what does a boolean do with a code block which is an argument to a ifTrue: message? It depends on which boolean! If the object is the "true" object, it executes the code block it has been handed. If it is the "false" object, it returns without executing the code block. So the traditional "conditional construct" has been replaced in Smalltalk with boolean objects which execute the indicated code block or not, depending on their truth-value.14
In the case of our example, the actual code within the block sends an error message to the current object. error: is handled by the parent class Object, and will pop up an appropriate complaint when the user tries to write too many checks. In general, the way you handle a fatal error in Smalltalk is to send an error message to yourself (through the "self" pseudo-variable), and let the error handling mechanisms inherited from the Object class take over.
As you might guess, there is also an ifFalse: message which booleans accept. It works exactly like ifTrue:, except that the logic has been reversed; a boolean "false" will execute the codeblock, and a boolean
"true" will not.
You should take a little time to play with this method of representing conditionals. You can run your checkbook, but can also invoke the conditional functions directly: 14 It is interesting to note that because of the way conditionals are done, conditional constructs are not part of the Smalltalk language—they are merely a defined behavior for the Boolean class of objects.
-18-
true ifTrue: [ ’Hello, world!’ printNl ] !
false ifTrue: [ ’Hello, world!’ printNl ] !
true ifFalse: [ ’Hello, world!’ printNl ] !
false ifFalse: [ ’Hello, world!’ printNl ] !
6.2. Iteration and collections
Now that we have some sanity checking in place, it remains for us to keep a log of the checks we write. We will do so by adding a Dictionary object to our Checking class, logging checks into it, and providing some messages for querying our check-writing history. But this enhancement brings up a very interesting question—when we change the "shape" of an object (in this case, by adding a new instance variable to the Checking class—our dictionary), what happens to the existing class, and its objects?
The answer is that the old objects continue to exist with their current shape.15 New objects will have the new shape. As this can lead to very puzzling behavior, it is usually best to eradicate all of the old objects, and then implement your changes. If this were more than a toy object accounting system, this would probably entail saving the objects off, converting to the new class, and reading the objects back into the new format. For now, we’ll just ignore what’s currently there, and define our latest Checking class.
Account subclass: #Checking
instanceVariableNames: ’checknum checksleft history’
classVariableNames: ’’
poolDictionaries: ’’
category: nil !
This is the same syntax as the last time we defined a checking account, except that we have three instance variables—the "checknum" and "checksleft" which have always been there, and our new "history"
|