We want to create a class that will represent temperatures. You might ask, "Why not just use the class float"? The answer is that the same temperature may be given on three different scales:
Once the temperature is initialized, we want to be able to use it in any of these scales. This means if we apply showIt to the following we should get 0 as our output.
|temp| temp := new Temperature fahrenheit: 32. temp centigrade.
For my application, I would like to be able to compare temperatures with temperatures and numbers, but I will not do arithmetic on temperatures. This determines the placement of the class Temperature. It will be a Magnitude, but not a Number.
The Temperature class may be created as a subclass of the Magnitude, by selecting Magnitude from the browsers class list and creating a new subclass using the Classes menu or the right button pop-up menu in the class pane.
Temperatures may be used any of the basic types, but we only wont to store the number once and use methods to convert to other scales. In our problem we decide the Celcius will be the most commonly used scale, so we store temperature in degrees Celcius. Once we have made this decision, we create an instance variable centigradeDegrees which will hold our temperature in degrees Celcius. As is always the case we create methods to access and set this variable.
We need to create instance methods to set and access the temperature as fahrenheit and kelvin. These functions use the following formulae
We will want class methods that create a Temperature object from fahrenheit, centigrade or kelvin values. These are very straight forward. We will actually add another type of class function to handle problems with the printOn: message. We want printOn: to print the temperature in the same units throughout a given part of the run. To do this we will create an indicator variable that is used to determine the current output scale. Because this indicator will apply to all of the Temperature objects, it will be a class variable and will require a class method to set it.
We will call our new indicator variable DefaultScale. Notice class variables start with a capital letter. The create the variable, click on the Temperature class to show its properties (as seen below). Then add the class variable and save the class.
Magnitude subclass: #Temperature
instanceVariableNames:
'centigradeDegrees '
classVariableNames:
'DefaultScale '
poolDictionaries: '' !
The class method to set the indicator follows. Note the legal values for the indicator are #kelvin, #centigrade, #fahrenheit, so our accessor (which is also a class method) must return one of these values even if the default has not been set. I have it return #centigrade.
printDefault: aSymbol
"Change the current print default (if legal)"
(aSymbol = #kelvin) | (aSymbol = #centigrade) | (aSymbol = #fahrenheit)
ifTrue: [DefaultScale := aSymbol]
ifFalse: [self error: 'incorrect default scale']
"These are examples of how the printDefault message is used."
"Temperature printDefault: #centigrade"
"Temperature printDefault: #fahrenheit"
"Temperature printDefault: #kelvin"
printDefault
"Return the current print default"
DefaultScale isNil
ifTrue: [^#centigrade]
ifFalse: [^DefaultScale]
By now you have noticed the weird conditionals. For a complete introduction to conditionals click here . Notice that conditionals are formed in a typical object-oriented way. A boolean object is created as the result of one or more messages applied on one or more objects. The result of this is either the boolean object true or the boolean object false. The messages ifTrue and ifFalse are used to determine which object was created and depending on which was created an appropriate block of code is executed. The printOn: message is another good example of the use of conditionals.
printOn: aWindow "I print myself in the form 'xxx degrees yyy where yyy is either 'kelvin', 'fahrenheit' or 'celcius'." |degrees scale| scale := self class printDefault. scale = #kelvin ifTrue: [degrees := self kelvin]. scale = #centigrade ifTrue: [degrees := self centigrade]. scale = #fahrenheit ifTrue: [degrees := self fahrenheit]. degrees printOn: aWindow. aWindow nextPutAll: ' degrees ';nextPutAll: scale
You can test your methods by applying DoIt and ShowIt to various parts of the following transcript entree.
Once you have create a subclass of a given class you must respect the wishes of the parent class. This means that you should implement all functions that the parent class designates as implementedBySubclass. The comparision operators and hash method are the methods so specified by Magnitude. We don't want a hash function so we leave it undefined. This will cause an error if a program tries to hash a Temperature, but that's ok.
< aMagnitude
"Answer true if the receiver is less
than aMagnitude, else answer false."
^self implementedBySubclass
We want to be able to compare numbers to our temperatures (assuming all our numbers during a given run represent temperatures in a single scale). The method simply compares centigrade values in the case of two temperatures, but creates a temperature with the appropriate scale if we have a number. We need to test if we have a temperature. The isTemperature message is defined to return true in the Temperature class, but is not defined elsewhere. We define it to return false in Magnitude so appropriate choices can be made. Also notice if we can't compare we call on the error message to bring up the debugger.
< aTemperature
"Is self less than aTemperature"
|scale loc|
scale := self class printDefault.
aTemperature isTemperature
ifTrue: [ ^( (self centigrade) < (aTemperature centigrade)) ]
ifFalse: [ aTemperature isNumber
ifTrue: [ loc := Temperature new.
scale = #centigrade
ifTrue: [ loc := Temperature centigrade: aTemperature ].
scale = #fahrenheit
ifTrue: [ loc fahrenheit: aTemperature ].
scale = #kelvin
ifTrue: [ loc kelvin: aTemperature ] .
^(self < loc)
]
ifFalse: [self error: 'Can only compare temperatures and numbers.']
]
You may save the image of the file you have created if you want to begin work where you left off, but in most cases you will just save the class. This is done by selecting File Out from the Class menu while you have the class you want to save selected. You will be asked for a file name with a Save File Dialog. Just use the suggested name.
The next time you want to work, you need only select Install from the File menu. You can select your file from the Open File Dialog and Smalltalk will do the rest.
Click here to retrieve both files needed for this program.