There are many objects in the Smalltalk Environment. Objects that respond to the same messages in the same way are grouped together. When they are grouped together, their private memory is represented in the same way and their methods refer to their data with the same set of names. A group of objects related in this way is called a class. Objects in a group are called instances of the class. Programming in the Smalltalk Environment consist of creating new classes, creating instances of classes, and specifying a sequence of message exchange among all of these objects.
Previously we have discussed the behavior and state of instances of classes. The Smalltalk Environment has the ability to specify a behavior and state for a class rather than an instance of a class. The definition of class behavior includes these three parts: messages, methods, and variables. These three parts are also in instance behaviors.
Methods and variables for class behavior are called class methods and class variables, respectively. Class variables have the same naming conventions as instance variables except that the first letter of a class variable must be a capital letter. Class methods on the other hand have the same naming conventions as instance methods.In Chapter 6 "Inheritance", the class variables are shared by multiple classes. For this reason, the class variable name must start with a capital letter.
Instance methods and class methods can refer to class variables, but only instance methods can refer to instance variables. The following diagram will display this example:
The class is a receiver of a class message. For example, this statement sends the class message new to the class Customer:
Example 1: Customer new
Everything in Smalltalk is an object including the class definition, which happens to be an instance of a class called Metaclass. From this, we see that a class message is being sent to an object, which in this case it happens to be an instance of the class Metaclass. It is not important for the beginning programmer to understand how class definitions are managed. What is important to understand is that the class definition, and the definition of class methods and variables, is an object.
Examples of Class Behavior
The new message from example one above is an example of class behavior. It creates a new instance of the class that receives the message.
Here is an example of maintaining a list of all instances of a class. In this example NCSU Registration wants to keep a list of all of its students. There are many ways to do this, but one way is to add class behavior to Student that keeps track of all Student instances. With this in place, we will be keeping track of students in a list and returning the contents of that list. The following is how we define the methods that are required:
Method Description addStudent Creates a new student and add it to the list. removeStudent: Removes a customer from the list. listOfStudents Returns the list of students.
We will define a class variable called ListOfStudents that point to the list of students. Here is the code for each of these class methods:
addStudent
"Create a new instance of myself and add it to the list of students."
| aStudent |
aStudent := self new.
self listOfStudents add: aStudent.
^aStudent
removeStudent: aStudent
"Remove a student object from the list."
^self listOfStudents remove: aStudent
The removeStudent: method uses the message remove: to remove a student from the list. If the student is not found in the list, the method will display a Debugger window. If it is possible that this student object would not be in the list, then an error is not acceptable. In this case the method should use the message remove:ifAbsent:. This message allows the ability to specify a block of code to run in case of an error. The following statement specifies a block with no statements, which will cause the error condition to be ignored.
^self listOfStudents remove: aStudent ifAbsent: [ ].
listOfStudents
"Return the list of Students."
ListOfStudents isNil
ifTrue: [ListOfStudents := OrderedCollection new].
^ListOfStudents
The following diagram illustrates the class definition for Customer:.
Here is an example of class methods maintaining common information which is needed by all instances of the class. For example, the class Float has a class method that returns the value of pi. The value of pi is needed by many instance methods for Float. This information doesn't change from instance to instance so it belongs as a class responsibility and not as an instance responsibility.
Any method can get the value of pi by sending the pi message to the class Float. It is shown as follows:
Float pi
Classes are created in the development environment using one of the class or application browsers. The steps are as follows:
1. Select the class that is to be the parent of the new class. 2. Ask that a subclass be added. 3. When prompted for the name, enter one; don't forget that the first character must be upper-case. 4. When prompted for the type of subclass, select subclass.
When you are finished, the browser window will hold an expression in the text area. This expression was used to create the class. It can now be modified to change and extend the class. Let's say, for example, that the expression for a new class is TrainSet. What will it look like in the browser? It will look like the following:
Object subclass: #TrainSet instanceVariableNames: ' ' classVariableNames: ' ' poolDictionaries: ' '
The instance variables are added by adding names to the quoted string following instanceVariableNames:. It will look like this in the browser:
Object subclass: #TrainSet instanceVariableNames: 'engine cars caboose track crossings ' classVariableNames: ' ' poolDictionaries: ' '
If the new class TrainSet has a pool dictionary or a class variable, they are added in the same way as above. With the changes, the browser will look like this:
Object subclass: #TrainSet instanceVariableNames: 'engine cars caboose track crossings ' classVariableNames: ' TrackLayout ' poolDictionaries: ' Trains '
The Class Definitions in the IBM Smalltalk Environment are created with interactive tools. In this environment methods have a text format, but classes do not, however,it is possible to save a class definition to a text file. This text file contains messages and text that will recreate the class in another Smalltalk image.
Using SalariedEmployee as an example, the following looks like this in external format:
"A subclass of Employee that adds protocol needed for employees with salaries" Employee subclass: #SalariedEmployee instanceVariableNames: 'position salary' classVariableNames: ' ' poolDictionaries: ' ' ! ! SalariedEmployee publicMethods ! position: aString position := aString ! position ^position ! salary: n salary := n ! salary ^salary ! !
Here is an explanation for the previous code. The expression starting with Employee subclass: defines the class Employee. This is the same expression that is found in the class or application browsers in the development environment.
The next two lines define a new class, SalariedEmployee, as a subclass of Employee, and has defined two instance variables. The next two lines define any class variable or pool dictionaries. The explanation point, often refer to as a bang, ends the definition.
The next line starts and ends with a bang. It specifies the class and the type, either public or private, of methods that follow. Finally, comes the methods, each ending with a bang. After the last method, another bang is use to indicate the end. With this format, no one ever has to write code. It is the export/import format which is used by IBM Smalltalk.
In Smalltalk there are many class methods that deal with the initializing of instances. Initializing instances is a very easy process which you will see in the following examples.
Examples of Initializing Instances
In the these examples, we will look at the these class methods; key: for the Association class and sortBlock: for the SortedCollection class. In both of these examples, the instances are created and data is set in each instance:
key: newKey
"Answer a new Association with an Object, with newKey as the association key."
^self new key: newKey
sortBlock: aBlock
"Answer aSortedCollection such that its elements will be sorted according
to the criterion specified by the two-argument block aBlock. aBlock must also
return a boolean."
^self new sortBlock: aBlock
To initialize class variables, you need to do it prior to their use. One way to do this is using what is called lazy initialization. Let's look at the listOfCustomers method for Customer:
Examples of Initializing of Class Variables
listOfCustomers
"Return the list of customers."
ListOfCustomers isNil ifTrue: [ListOfCustomers := OrderedCollection new].
^ListOfCustomers
The method ensures that the value of the ListOfCustomers instance variable is always valid. In other words not nil. This scheme works as long as no other method has a direct reference to the variable.
Another way to initialize a class variable is to add a new class method that initializes all class variables. This class method would need to have been run once before sending any other class messages to this particular class. The best way to call this class method is to run code from a Workspace that sends this message to this class.
In this example, let's use again the following method for Customer:
initialize
"Initialize all class variables."
ListOfCustomers := OrderedCollection new
The Customer class needs to receive an initialize message before it can receive any other class messages that depend on the setting of this class variable. The programmer or developer can run the following statement in a Workspace.
Customer initialize.
In this section of the document we will be using the Customer, Vendor and the Person classes. Let's say that we wanted three separate lists for Customer,Vendor, and Person instead of one. How do we do this? One way is to add class variables to each of these three classes and defining duplicate class methods to manage each list. Another way to do this is to define the variable as a class instance variable as oppose to a class variable.
Class instance variables are like class variables except that each subclass has its own copy. Since it is a private variable, the class instance variable must start with a lowercase letter. Class instance variables support inherited class behavior, with each class having its own state.
Class instance variables are instance variables that define the state of the class rather than the state of an instance of a class.
Example of Class Instance Variables
Lets change the variable Persons from a class variable to a class instance variable. It now has a name of persons. To access the data in persons from instance methods, we must send a message to the class.
persons
"Return the contents of persons and make sure that the variable is initialized."
Persons isNil ifTrue: [Persons := Set new].
^Persons
This design now supports three separate lists with only one set of class methods. All three classe,Customer,Vendor, and Person, receive their own copy of this class instance variable, which makes it unique for all of them. There needs to be only one set of class methods which is defined in Person. The following diagram illustrates this example:
Both class variables and class instance variables are important in developing complex Smalltalk applications. Class variables allow common sharing of information across a range of subclasses. Class instance variables allow inheritance of class behavior across the range of subclasses, while allowing each subclass to manage its own class state.
We have discussed the following in this chapter: