Help - Programming - Bankaccount
Previous - Programming - Next
Tutorial: Making a BankAccount class
by John Maloney
(after the example in Goldberg and Robson)
Updated to work with Squeak 3.7 in Squeak Help
by Maarten Maartensz
This tutorial is meant to be used in a Morphic Project. To begin a new Morphic Project, simply hold the mouse button down on the main Squeak window background (away from any other windows it contains) to pop up the screen menu, move your mouse pointer down to "open...", and release the mouse button to select it; this will pop up another menu - click on "project (morphic)". A small orange window titled "Unnamed" appears. Click once in the middle of it to zoom into the new, blank Morphic Project (known as a Morphic "World") - it will fill the entire main Squeak window with a light gray background. (If you are not sure where you are, just pop up the screen menu - if its title is "World", you are already in a Morphic Project.)
To learn how to exit this new project or save your work as you go along, see the explanation at the end of this tutorial.
Let's break the ice by writing a really simple program. To begin, we need to open a system browser. From the screen menu, select "open...", then click on "browser". A System Browser appears.
This is a little large, so we want to resize the window. Move the mouse down to the bottom right corner -- a little yellow dot will appear on the edge of the browser window as the cursor crosses it. This dot is used for resizing/repositioning any line it appears on. Just hold your mouse button down on the dot and drag the window's outline to the size you want - then release the mouse button. (If you move the cursor around slowly inside the browser, you will see a little yellow dot appear on most of the lines that separate the parts (panes) of the browser window -- drag any of these dots to resize the panes relative to each other. For some panes you might need to move the mouse from left to right to see the yellow dot.)
In an object oriented system, the logical thing to do is to build a really simple object. It will be an instance of a new class we will create. But first, we need to make a place to put our new class. To do this, we will add a new class category (also known as a system category). Class categories are found in the top left pane of the browser. When you put your cursor in this pane you will see the scroll bar pop out on the left side of the pane. Go up to the little box with a dash in it - at the top of this scroll bar. You will see a little menu icon pop up when you put the cursor on it -- hold the mouse button down to get the menu for the class category pane, move down to "add item ...", and release the mouse button to select it (you can also hold down the right mouse button in the class category pane to pop up this menu if you're not on a Macintosh computer) . A small prompter window pops up to let you type in a new class category. Type in "My Stuff" (without the double quotes) and click on Accept, or just hit the Enter key. Your new class category will be added to the end of this whole list of class categories -- just scroll down to the bottom to see "My Stuff" - it should already highlighted (if not, just click on it). (Categories are added in the order they were created, and are not sorted alphabetically. Later you'll discover a menu item that lets you rearrange them yourself.) Right after you created your new class category, the bottom pane of the browser (the code pane) changes to:
Object subclass: #NameOfClass instanceVariableNames: 'instVarName1 instVarName2' classVariableNames: 'ClassVarName1 ClassVarName2' poolDictionaries: '' category: 'My Stuff'
This is a template that lets you define a new class. First we will edit "NameOfClass" to be the name we want to give our new class. Double-click on "NameOfClass" to select it and type "BankAccount". (Like many text editors, if you select something and start typing, it replaces the current selection with what you type.) Next I will select "instVarName1 instVarName2" (if you double-click just inside a delimiter, such as a single quote, you can select the whole contents at once) and type "balance" there instead. Our goal here is to create a kind of object called a BankAccount, that is, a class of objects called BankAccount. I want those objects to have one piece of state, one instance variable, which I'll call "balance". I don't want it to have any class variable names, so I'm going to select "ClassVarName1 ClassVarName2" (inside the single quotes) and delete them by hitting the Backspace key (leaving just the empty single quotes behind). Now you should see:
Object subclass: #BankAccount instanceVariableNames: 'balance' classVariableNames: '' poolDictionaries: '' category: 'My Stuff'
At this point, you've created the description of your new class. It is a subclass of Object. Object is the grandfather of all objects in the system. The thing before "subclass:" is the superclass, and the thing after is the new class I'm creating. It has a single instance variable called "balance". It doesn't have any class variables or pool dictionaries, so don't worry about what those are right now. Your new class will be in the category "My Stuff".
Notice that the code pane has a red line running around its inner boundary -- this means that your class definition still needs to be saved. To save our work. we need to accept our new class definition: put your cursor in the code pane and hold down the right mouse button [Option-click on Macintosh computers - Macintosh key sequences will be shown this way in the rest of this tutorial] and select "accept" from the code pane menu - or just type Alt-s [Command-s]. Now you should see "BankAccount" appear highlighted in the second pane (the class names pane).
Now we have this new kind of object, but it doesn't actually do anything yet. So we're going to start with our empty class definition and add to it.
Let's open up another kind of window -- called a workspace. It is just a scratch text pane where you can type snippets of code and try them out. So again, I go to the screen menu, select "open...", and click on "workspace". I'm going to resize it, just as I did with the browser, then move it under the browser and over on the right side (you can drag a whole window around by holding the mouse button down on the window's title bar - away from the title, and the X and O - and dragging the outline to its new position).
It's okay to overlap the browser and the workspace -- just click on a window to bring it to the front.
Select and copy this text:
b _ BankAccount new.
Put the cursor in the workspace window and paste the text into it by typing Alt-v [Command-v]. Remember, all of the editing commands also appear on the right button [Option-click] menu for the workspace .
Notice that the underbar or underscore character in the first line you just copied over has changed to a left-pointing arrow in the workspace window. This is Squeak's normal assignment operator and you type it in as an underbar (shift-minus). As an alternative, you can also use colon-equals ":=" for the assignment operator (which may be familiar to you from other languages like Pascal).
Select the first line, and choose "printIt" from the right button menu (the menu is also at the top of the scroll bar). This sends the message "new" to the BankAccount class, which responds by creating a new instance of itself (a new BankAccount object), which is then assigned to the variable "b" -- then the highlighted text "a BankAccount" is printed right after the line you selected - telling you what was returned by the expression (just hit the backspace key to delete the extra text).
We've defined a class BankAccount, which doesn't do anything yet, and we've just made an instance of it. We'd like to look at our instance. There's a message "inspect" that can be sent to any object in the system. Select the second line, "b inspect" and choose "doIt" from the workspace menu. An inspect window titled "BankAccount" will pop up. In its top left pane, click on the instance variable "balance" - this will show balance's value in the top right pane -- we see that it is "nil". (Leave this inspect window open and visible - we will refer to it later.)
Note, "nil" is actually an object, just like everything else in Squeak. It's an instance of a class called UndefinedObject. We use nil to refer to any variable that hasn't yet been assigned (initialized to) another value. So, by default, every variable in Smalltalk is always initialized -- unlike C, where, depending on what the last random garbage on the stack was, your variable could have any value in it. Smalltalk, and Squeak is a kind of Smalltalk, always initializes its variables, so you can count on your variables having a repeatable value. And if you haven't done anything else, it will be nil.
We want to make this object do something -- so let's make it return the balance. So I'll start by just sending the message "balance" to "b". I actually know it's not going to work, but let's see what happens. Copy this over to your workspace:
Select this, and choose "printIt". A menu pops up telling you that "balance" in an unknown selector and offering some possible choices to select instead. Nobody's ever defined the selector "balance" in this Squeak before. So I'll say, "yes, I really did mean balance" by clicking on "balance" in the list the menu gave me.
Then you see a small red walkback window appear. It tells you that that something didn't understand the message "balance", and displays some of the stack showing the order of message sends that led up that point. doIt is here, and that is where the code actually is. The top line, here, says BankAccount, and in parentheses, it says "Object", because BankAccount is a subclass of Object. A BankAccount doesn't understand the message "balance". Since we know what the problem is, we need not go any further here -- just click on the X on the left side of the title bar to close the window.
Come over to the browser and go to the third pane from the left -- this is the message category pane. Click on the "no messages" category to select it. Now you see a new template appear in the code pane:
message selector and argument names
"comment stating purpose of message"
| temporary variable names |
This is a template for messages, just like we had a template for classes. Select the first line (hold your mouse button down and drag your cursor through the text on that line to select it all, then release the mouse button) and type in "balance", the name of this method. Next is a place to put a comment, between the double-quotes. In some cases, when the meaning of a message is obvious, you might not put in a comment. I'll type, "Return the balance". Now I'll put the code in. What I want it to say is, 'return the contents of my instance variable, balance'. We won't have any temporary variables in this method, so delete the next line entirely (vertical bars ("|") and all). Now I type in "^ balance", the body of the method. This little up-arrow, here, which you get by typing the carat (circumflex, up-arrow, shift-6), is the way Squeak says Return (more precisely, it says "return the value of the following expression").
balance "Return the balance" ^ balance
When that looks right (except "balance" will not be bold yet), choose "accept" from the code pane menu. This little window pops up saying, 'Please type your initials'. What does that have to do with accepting a method? Well, the reason it asks for your initials is because it's going to tag all of the changes you make to the system with your initials (until this is changed). Just type your initials and hit the Enter key or click the "Accept" button (now you should see "balance" change to "balance"). (Once someone has entered their initials in a Squeak image and saved the image, it does not ask again.) Now that we have added a new method, notice that "no messages" in the message category pane has changed to "as yet unclassified".
Let's go back to the workspace and send the message. Select "b balance" again and choose "printIt". It returns an uninteresting value, nil. We knew, from looking at the inspector, that we'd get that. We'd like to get a more interesting value into there. Let's add an initialize method. Back in the Browser, select everything in the bottom pane (just double-click in the code pane well below any text it may contain) and replace it with:
balance _ 0.
I'm getting tired of selecting "accept" from the menu, so I'll do Alt-s [Command-s]. So any modified text that's in the bottom pane, becomes a new method when you accept it. It doesn't matter if 'balance' was the old method. It looks at the keyword at the top of the method text to decide what method it is. A little thing to remember is that a category must be selected in third pane of the browser when you accept, or it won't process your text as a method.
Now we have two methods here. I'm going to actually send the message "initialize". By the way, the "initialize" message is a Smalltalk idiom. Typically you create an object, or define a class, and one of the first things you do is define some "initialize" method, which will give the instance variables that you've defined some meaningful initial value. In our case, the initialize method sets the balance to zero. Now type this in the workspace, select it, and choose "doIt".
Notice that the balance in the BankAccount inspector we opened earlier changes from nil to 0. Or, we can also select "b initialize" and type Alt-p [Command-p] for "Print It" -- which tells us once again that the balance is now zero.
One of the nice things about the Morphic world is that a window can be updating itself all the time. Things like inspectors really do try to keep themselves up to date. Even though this wasn't the active window, because it wasn't selected, it did update the balance, and we can see that the balance is zero.
We're actually almost done making a working object here. Select all the text in the bottom pane of the browser again and replace it with:
balance _ balance + amount.
This one is the "deposit:" method. And it's only one line long. Obviously, deposit will just increment the balance by the amount of the deposit. Now "accept" that. In the workspace, type:
b deposit: 10.
Select that and do Alt-d [Command-d] to doIt -- notice that the balance immediately incremented itself over here in the inspector to show 10. Now let's paste
b deposit: 100 factorial.
into the workspace and select it and do Alt-d [Command-d]. That might be more money than there is in the world, though. (I love seeing the 10 from our previous balance added at the end of that number.)
Now we've built a bank account. It's got three methods: an initialize, a way of returning the balance, and a way of depositing.
I guess I should back up a little and say something about how objects in Squeak define their own interfaces to the world. One of the things that they won't let you do is look at the values in their instance variables without sending them a message. (See encapsulation.) Unlike a record or a structure or something in C, before I could find out the value of this instance variable, I actually had to implement the message, "balance". That is called 'information hiding'. It means that you can change your mind about the representation of a BankAccount rather easily, because you know that the only way people can be depending on it is through the message interface.
For example, I could have a different kind of BankAccount, which was something that depended on the current value of a basket of stocks or something. I could still ask for the balance of that Bank Account, by sending it the message "balance". But instead of returning the value of an instance variable, it might multiply the number of shares of stock I have by their current prices. The nice thing about that is, if I made this kind of BankAccount, everybody who is using the old kind of BankAccount would not have to change their code to accommodate it. Since they were only sending the message, balance, not knowing anything about its internal structure, they would always get the right answer.
Back to Syntax for a moment. Some of the lines end with period. Period, in Smalltalk, is a lot like semi-colon in C. It's the statement separator. In this case, since there's only a single statement, you don't actually need it. I usually leave the period there in case I later add another line to the method.
One more thing about this browser window. Remember that if there's unaccepted text in it, it will show this tiny little border of red. That means that I have edited this method, but I haven't yet accepted it. It's possible that I don't really want to accept it. If I just evaluated a "printIt" in it, I don't really want to keep the changed text -- I want the original thing. So there's a command called "cancel", right under Accept, and there's a shortcut for it, which is Alt-l (lowercase L, not a one) [Command-l]. That will undo all the edits I made and revert to the previously saved version of this method.
Now let's add a "withdraw" method -- withdraw is just like deposit, except that it checks for a negative balance.
amount > balance ifTrue: [^ self inform: 'sorry, not enough funds'].
balance _ balance - amount.
The next thing I want to do is to show how you can evolve an object, even if you already have an instance of it. This is a hard thing to do in a language like Java or C. I am going to change this object on the fly, to maintain a history of the BankAccount balance.
In the browser, click on the "instance" button below the class names pane (the second pane). You'll see the class definition of BankAccount in the bottom pane again. I want to add a new instance variable, called "history". Click right after "balance" and type in "history" after that.
Object subclass: #BankAccount instanceVariableNames: 'balance history ' classVariableNames: '' poolDictionaries: '' category: 'My Stuff'
Again, you can see that it's not accepted yet because it's still got the red around it. Do Alt-s [Command-s] to accept the new class definition. The existing instance, b, already has a new instance variable that's been added and initialized to nil. But the inspector isn't quite smart enough to figure that out. So, close that inspector, select a "b" anywhere in the workspace window, and do Alt-i [Command-i] to get a new inspector. Now we can see our new instance variable, history. All existing instances are changed when you accept a change to the class's shape. (You can have as many different inspectors open as you wish - we only closed the old inspector here because it was showing an obsolete view of our instance.)
By the way, you might wonder what kind of a variable "b" is. Workspaces have a special dictionary of variable bindings associated with them. So, I didn't have to declare "b" in any way. We also have global variables. By convention, global variables start with capital letters, but locally scoped variables, in methods and workspaces, start with a lower case letter.
A lot of the style of programming in this system is "late-binding programming". You can reformulate things that you've already created. And the workspace is the place to do it. When you are using the System Browser, your tendency is to think top-down. It organizes the world in a top-down way. And yet, a lot of times, you're not sure exactly how you're going to partition things. So, the way we typically program, even the real expert programmers, is to do a lot of messing around in workspaces. And you use the local dictionary that's there for variables until you get a sense of where they really belong. And then a lot of times, you just copy your code into the Browser. Sometimes we call that sort of thing "scratch coding". Most people are not good enough to think top-down all the time. We certainly aren't good enough, so we think in a quick-and-dirty way in workspaces. We mention this because this style of development is not very apparent in this tutorial -- we're essentially presenting a "finished" program, so you don't really get to see how we got there.
Let's just finish this example. We have this 'history' instance variable, but again, it was initialized to nil. What we really want is to keep a list of balances, so we need to put an object in "history" that can hold a list of items in the order it receives them.
balance _ 0.
history _ OrderedCollection new.
Click on "initialize" in the fourth pane of the browser (the message selector pane), add this new last line to the method, and accept it. Don't forget the period after the first line. I've added "history gets OrderedCollection new". OrderedCollection is a class in the system. And "new" is creating a new instance of it. OrderedCollections are a type of dynamic array. You can add new things to the end easily, and they just keep resizing themselves, indefinitely.
Now I'll come back to my workspace and select
and do Alt-d [Command-d] to "Do It". You see that over in the inspector, 'history' has an empty OrderedCollection in it. If there were something in the OrderedCollection, it would appear between the two parentheses. Our next job is to make something appear there. We want to add things to the history when the balance changes. There could be many places where the balance is changed, so let's create a single method that they all call. It's a choke-point for setting the balance -- any method modifying the balance should use this method so a history can be kept of all balance changes.
balance _ newBalance.
history addLast: newBalance.
In this method, we store into the balance, and also append the new balance to the history. Now we need to modify the other two methods to use the 'balance:' method.
self balance: balance + amount.
And now modify the other one.
withdraw: amount amount > balance ifTrue: [^ self inform: 'sorry, not enough funds']. self balance: balance - amount.
Both of these methods do the same thing as before, but now, because they both use the "balance:" method to set the balance, they also append the resulting balance to the history. Let's try depositing stuff again. Paste this into your workspace.
b deposit: 100.
Select the 'history' variable in your inspector, then select "b deposit: 100" in your workspace and doIt again and again. As you do repeated deposits, you'll see the history build up in the inspector.
I'd like to see that history graphically. To do this, I'm going to add one more method to BankAccount.
|Note: if you use a Squeak before version 3.0, somewhat different code should be used instead of that presented in the main text.|
historyMorph "displays barchart, width 30 per bar" | bars m | bars _ history collect: [:v | Morph new extent: [email protected]]. m _ AlignmentMorph newRow hResizing: #shrinkWrap; vResizing: #shrinkWrap; cellPositioning: #bottomRight. m addAllMorphs: bars. ^ m
The goal is to produce a Morph containing a bar graph. Each bar is a generic Morph of the right size. The variables between vertical bar characters are being declared as local (temporary) variables. We have two of them, 'bars' and 'm'. 'bars' will be a collection of Morphs, whose heights are the bank balance at various times.
We send history the 'collect:' message. Collect makes a new collection of the same size as the object it was sent to (which is history). The block, in square brackets, is evaluated once for each element of history. The value comes into the block local variable 'v'. A new Morph is created. '@' is an operator that makes an X-Y point from two numbers. This is used to address the screen. You see that the width will be 30 and the height will be 'v'. 'extent:' tells the Morph what size to be. The result of the last expression in the block is what is put into the new collection. bars ends up with a bunch of Morphs in its variable m, one for each balance in history, which it returns.
On lines 4-7, we make an AlignmentMorph to hold all the bars, each of which will be a sub-morph of it. On the eighth line, we add the bars as submorphs. And finally we return the AlignmentMorph, m.
b historyMorph openInWorld.
Type these lines into the workspace. Select only the FIRST line and do a printIt. It returns an AlignmentMorph. Well, that's not very interesting, just printing it out, so I'm going to send the AlignmentMorph the message, openInWorld. When you execute the second line, it will cause the AlignmentMorph to get created and displayed on the screen. It looks like a bar graph.
This "bar graph" is basically a composite object which you can pick up and move around. It's got little objects in it. These are the Morphs of the individual bars. To manipulate these Morphs you need to get a Morphic halo (a set of colored dots surrounding the selected morph) by doing Alt-click [Command-click] on one of the bars. Below the bar it says it's a Morph -- and it's a separately manipulable object. You can actually resize it with the yellow handle and even grab that bar out of there by picking it up by the black handle, or do other things to it. For any of the halo handles, if you pause over it with the cursor, it will bring up a balloon with information in it.
One more thing, to delete the entire graph, Alt-click [Command-click] on the graph as a whole, away from a bar. Make sure it says AlignmentMorph at the bottom. There's a little pink "X" label in the upper left part of the halo - click on the X to delete the whole graph.
Should you wish to leave your new World at some point, just bring up its screen menu and select "previous project" -- this will return you to the project in which you were created this new Morphic Project -- with the addition of a small window containing a thumbnail image of your Morphic World. Just click on the thumbnail image to return to your World at any time.
As you work through this tutorial, we recommend you save your work from time to time. You can do this - and indeed save the state of your entire system (the image) - by popping up the screen menu and selecting "save". When you are ready to close Squeak, select "save and quit" -- then, when you start Squeak again, it will open up exactly where you were when you quit.
Previous - Programming - Next