========================================= Notes for: Date today. 3 May 1996 ========================================= Administrative: Laptop update: Machine is here, but braindead. Deployment on Monday is a definite possibility. Problem set S1: out now. Due Monday 5/13. ========================================= Today: Finish up with the Measurement class (formerly the temperature class): Printing Programming a Stack. ========================================= Notes on displaying objects: -- There are a number of ways to display the result of a computation. Easiest is just to use the "Print it" selection, which will write the computation result into the current window. The obvious advantage is that it's easy and direct. Disadvantage is that you don't get to choose where the output is going (i.e. you can't direct it to an arbitrary stream) and you can only print a limited amount of information conveniently. In particular, you can't print intermediate calculations---only the end result. A slightly more flexible approach is what you are using in the problem set: associate a stream/window pair with the application, then all objects can print to that stream. (Technically it's not a stream, but a "TextCollector"). That's a little more flexible mainly in that intermediate calculations can be printed. Finally we have the full generality of a graphical UI, which we will begin studying soon. In class today we just studied the first method. (The second is quite similar anyway.) How do you customize the print method for an object? The simple answer is that when an object needs to print, it is sent the message object printOn: aStream where the default is to print 'a ', followed by the object's class name. Actually many objects over-ride this policy: numbers, for example show some digits that convey the numberic value, and collections often print a short version of what they contain. So to customize the way something is printed, you just have to override the printOn: method. All you need to know is how to print things to the stream argument "aStream," and the easiest answer to that is that a stream responds to nextPut: aCharacter and to nextPutAll: aSequenceOfCharacters where the latter just repeatedly calls nextPut: on the elements of the sequence. The rub here is that the argument must be a character or string of characters, so our first cut at printing our measurement: aStream nextPutAll: value, ' ', scale. won't work because value is an integer and scale is a symbol. (I'm ignoring the fact that we would also have to convert 'value' from internal units to 'scale' units to get meaningful output.) Fortunately we have the method printString, which is defined for every object type, and produces a string representation of the object. So the code aStream nextPutAll: (value printString), ' ', (scale printString). will work, as all three arguments will be strings, and the comma operator concatenates string together, so the single argument to nextPutAll: is indeed a string. I also pointed out an alternative way of writing this same code, using the "cascade" operator that sends a series of messages to the same object: aStream nextPutAll: (value printString); nextPutAll: ' '; nextPutAll: (scale printString). This has exactly the same effect, but sends three, rather than 1, messages to aStream. On the other hand, it avoids building that next string. Notice that the separator here is a semicolon instead of a period. =============================================== =============================================== Building a Stack data structure. This was really just a lesson in how powerful inheritence could be. Recall that we want our Stack object to respond to: new -- create an empty instance (class method) isEmpty -- boolean push: anObject pop -- return the object By making Stack a subclass of OrderedCollection, we inherit some useful methods: new -- create an empty instance (class method) isEmpty -- boolean addFirst: anObject removeFirst -- return the removed object The first two do exactly what we need, so we don't need to write any code at all. Stack will inherit them directly from OrderedCollection. For the last two the code is trivial push: anObject self addFirst: anObject pop ^ self removeFirst. The interesting thing here is that by inheriting from OrderedCollection, Stack in effect *becomes* an OrderedCollection. You can directly apply to it any message that you could apply to its parent. It was pointed out that there are some things that Stack should *not* inherit from OrderedCollection. addLast:, for example. In that case we have three options: -- Ignore it. Just hope that nobody actually uses addLast: -- Explicitly override the method: addLast: anObject self error: 'Stack should not implement this method.' If there are lots of these, this could become tedious! -- Hide the collection within an instance variable. Make Stack a subclasss of Object, and give it an instance variable called 'data'. Then explicitly define all the instance methods: initialize data := OrderedCollection new. push: anObject data addFirst: anObject. pop ^ data removeFirst. This works great if you only want to use a little of the functionality from OrderedCollection.