As we saw in Chapter 5, in Smalltalk, everything is an object, and every object is an instance of a class. Classes are no exception: classes are objects, and class objects are instances of other classes. This object model captures the essence of object-oriented programming: it is lean, simple, elegant and uniform. However, the implications of this uniformity may confuse newcomers. The goal of this chapter is to show that there is nothing complex, “magic” or special here: just simple rules applied uniformly. By following these rules you can always understand why the situation is the way that it is.
The Smalltalk object model is based on a limited number of concepts applied uniformly. Smalltalk’s designers applied Occam’s razor: any consideration leading to a model more complex than necessary was discarded.
To refresh your memory, here are the rules of the object model that we explored in Chapter 5.
As we mentioned in the introduction to this chapter, a consequence of Rule 1 is that classes are objects too, so Rule 2 tells us that classes must also be instances of classes. The class of a class is called a metaclass. A metaclass is created automatically for you whenever you create a class. Most of the time you do not need to care or think about metaclasses. However, every time that you use the browser to browse the “class side” of a class, it is helpful to recall that you are actually browsing a different class. A class and its metaclass are two separate classes, even though the former is an instance of the latter.
To properly explain classes and metaclasses, we need to extend the rules from Chapter 5 with the following additional rules.
Together, these 10 rules complete Smalltalk’s object model.
We will first briefly revisit the 5 rules from Chapter 5 with a small example. Then we will take a closer look at the new rules, using the same example.
Since everything is an object, the color blue in Smalltalk is also an object.
Color blue -→ Color blue
Every object is an instance of a class. The class of the color blue is the class Color:
Color blue class -→ Color
Interestingly, if we set the alpha value of a color, we get an instance of a different class, namely TranslucentColor:
(Color blue alpha: 0.4) class -→ TranslucentColor
We can create a morph and set its color to this translucent color:
EllipseMorph new color: (Color blue alpha: 0.4); openInWorld
You can see the effect in Figure 13.1.
By Rule 3, every class has a superclass. The superclass of TranslucentColor is Color, and the superclass of Color is Object:
TranslucentColor superclass -→ Color
Color superclass -→ Object
Everything happens by sending messagess (Rule 4), so we can deduce that blue is a message to Color, class and alpha: are messages to the color blue, openInWorld is a message to an ellipse morph, and superclass is a message to TranslucentColor and Color. The receiver in each case is an object, since everything is an object, but some of these objects are also classes.
Method lookup follows the inheritance chain (Rule 5), so when we send the message class to the result of Color blue alpha: 0.4, the message is handled when the corresponding method is found in the class Object, as shown in Figure 13.2.
The figure captures the essence of the is-a relationship. Our translucent blue object is a TranslucentColor instance, but we can also say that it is a Color and that it is an Object, since it responds to the messages defined in all of these classes. In fact, there is a message, isKindOf:, that you can send to any object to find out if it is in an is a relationship with a given class:
translucentBlue := Color blue alpha: 0.4.
translucentBlue isKindOf: TranslucentColor -→ true
translucentBlue isKindOf: Color -→ true
translucentBlue isKindOf: Object -→ true
As we mentioned in Section 13.1, classes whose instances are themselves classes are called metaclasses. Metaclasses are implicit. Metaclasses are automatically created when you define a class. We say that they are implicit since as a programmer you never have to worry about them. An implicit metaclass is created for each class you create, so each metaclass has only a single instance. Whereas ordinary classes are named by global variables, metaclasses are anonymous. However, we can always refer to them through the class that is their instance. The class of Color, for instance, is Color class, and the class of Object is Object class:
Figure 13.3 shows how each class is an instance of its (anonymous) metaclass.
Color class -→ Color class
Object class -→ Object class
The fact that classes are also objects makes it easy for us to query them by sending messages. Let’s have a look:
Color subclasses -→ {TranslucentColor}
TranslucentColor subclasses -→ #()
TranslucentColor allSuperclasses -→ an OrderedCollection(Color Object ProtoObject)
TranslucentColor instVarNames -→ #(’alpha’)
TranslucentColor allInstVarNames -→ #(’rgb’ ’cachedDepth’ ’cachedBitPattern’ ’alpha’)
TranslucentColor selectors -→ an IdentitySet(#pixelWord32 #asNontranslucentColor #privateAlpha #pixelValueForDepth: #isOpaque #isTranslucentColor #storeOn: #pixelWordForDepth: #scaledPixelValue32 #alpha #bitPatternForDepth: #hash #isTransparent #isTranslucent #balancedPatternForDepth: #setRgb:alpha: #alpha: #storeArrayValuesOn:)
Rule 7 says that the superclass of a metaclass cannot be an arbitrary class: it is constrained to be the metaclass of the superclass of the metaclass’s unique instance.
TranslucentColor class superclass -→ Color class
TranslucentColor superclass class -→ Color class
This is what we mean by the metaclass hierarchy being parallel to the class hierarchy; Figure 13.4 shows how this works in the TranslucentColor hierarchy.
Uniformity between Classes and Objects. It is interesting to step back a moment and realize that there is no difference between sending a message to an object and to a class. In both cases the search for the corresponding method starts in the class of the receiver, and proceeds up the inheritance chain. Thus, messages sent to classes must follow the metaclass inheritance chain. Consider, for example, the method blue, which is implemented on the class side of Color. If we send the message blue to TranslucentColor, then it will be looked-up the same way as any other message. The lookup starts in TranslucentColor class, and proceeds up the metaclass hierarchy until it is found in Color class (see Figure 13.5).
TranslucentColor class -→ TranslucentColor class
TranslucentColor class superclass -→ Color class
TranslucentColor class superclass superclass -→ Object class
Note that we get as a result an ordinary Color blue, and not a translucent one — there is no magic!
TranslucentColor blue -→ Color blue
Thus we see that there is one uniform kind of method lookup in Smalltalk. Classes are just objects, and behave like any other objects. Classes have the power to create new instances only because classes happen to respond to the message new, and because the method for new knows how to create new instances. Normally, non-class objects do not understand this message, but if you have a good reason to do so, there is nothing stopping you from adding a new method to a non-metaclass.
Since classes are objects, we can also inspect them.
Inspect Color blue and Color.
Notice that in one case you are inspecting an instance of Color and in the other case the Color class itself. This can be a bit confusing, because the title bar of the inspector names the class of the object being inspected.
The inspector on Color allows you to see the superclass, instance variables, method dictionary, and so on, of the Color class, as shown in Figure 13.6.
Every metaclass is-a class, hence inherits from Class. Class in turn inherits from its superclasses, ClassDescription and Behavior. Since everything in Smalltalk is-an object, these classes all inherit eventually from Object. We can see the complete picture in Figure 13.7.
Where is new defined? To understand the importance of the fact that metaclasses inherit from Class and Behavior, it helps to ask where new is defined and how it is found. When the message new is sent to a class it is looked up in its metaclass chain and ultimately in its superclasses Class, ClassDescription and Behavior as shown in Figure 13.8. The question “Where is new defined?” is crucial. new is first defined in the class Behavior, and it can be redefined in its subclasses, including any of the metaclass of the classes we define, when this is necessary. Now when a message new is sent to a class it is looked up, as usual, in the metaclass of this class, continuing up the superclass chain right up to the class Behavior, if it has not been redefined along the way. Note that the result of sending TranslucentColor new is an instance of TranslucentColor and not of Behavior, even though the method is looked-up in the class Behavior! new always returns an instance of self, the class that receives the message, even if it is implemented in another class.
TranslucentColor new class -→ TranslucentColor "not Behavior
"
A common mistake is to look for new in the superclass of the receiving class. The same holds for new:, the standard message to create an object of a given size. For example, Array new: 4 creates an array of 4 elements. You will not find this method defined in Array or any of its superclasses. Instead you should look in Array class and its superclasses, since that is where the lookup will start. Responsibilities of Behavior, ClassDescription and Class. Behavior provides the minimum state necessary for objects that have instances: this includes a superclass link, a method dictionary, and a description of the instances (i.e., representation and number). Behavior inherits from Object, so it, and all of its subclasses, can behave like objects. Behavior is also the basic interface to the compiler. It provides methods for creating a method dictionary, compiling methods, creating instances (i.e., new, basicNew, new:, and basicNew:), manipulating the class hierarchy (i.e., superclass:, addSubclass:), accessing methods (i.e., selectors, allSelectors, compiledMethodAt:), accessing instances and variables (i.e., allInstances, instVarNames …), accessing the class hierarchy (i.e., superclass, subclasses) and querying (i.e., hasMethods, includesSelector, canUnderstand:, inheritsFrom:, isVariable). ClassDescription is an abstract class that provides facilities needed by its two direct subclasses, Class and Metaclass. ClassDescription adds a number of facilities to the basis provided by Behavior: named instance variables, the categorization of methods into protocols, the notion of a name (abstract), the maintenance of change sets and the logging of changes, and most of the mechanisms needed for filing-out changes. Class represents the common behaviour of all classes. It provides a class name, compilation methods, method storage, and instance variables. It provides a concrete representation for class variable names and shared pool variables (addClassVarName:, addSharedPool:, initialize). Class knows how to create instances, so all metaclasses should inherit ultimately from Class.
Metaclasses are objects too; they are instances of the class Metaclass as shown in Figure 13.9. The instances of class Metaclass are the anonymous metaclasses, each of which has exactly one instance, which is a class.
Metaclass represents common metaclass behaviour. It provides methods for instance creation (subclassOf:) creating initialized instances of the metaclass’s sole instance, initialization of class variables, metaclass instance, method compilation, and class information (inheritance links, instance variables, etc.).
The final question to be answered is: what is the class of Metaclass class?
The answer is simple: it is a metaclass, so it must be an instance of Metaclass, just like all the other metaclasses in the system (see Figure 13.10).
The figure shows how all metaclasses are instances of Metaclass, including the metaclass of Metaclass itself. If you compare Figures 13.9 and 13.10 you will see how the metaclass hierarchy perfectly mirrors the class hierarchy, all the way up to Object class.
The following examples show us how we can query the class hierarchy to
demonstrate that Figure 13.10 is correct. (Actually, you will see that we told a
white lie — Object class superclass -→ProtoObject class, not Class. In Pharo, we
must go one superclass higher to reach Class.)
TranslucentColor superclass -→ Color
Color superclass -→ Object
TranslucentColor class superclass -→ Color class
Color class superclass -→ Object class
Object class superclass superclass -→ Class "NB: skip ProtoObject class"
Class superclass -→ ClassDescription
ClassDescription superclass -→ Behavior
Behavior superclass -→ Object
TranslucentColor class class -→ Metaclass
Color class class -→ Metaclass
Object class class -→ Metaclass
Behavior class class -→ Metaclass
Metaclass class class -→ Metaclass
Metaclass superclass -→ ClassDescription
Now you should understand better how classes are organized and the impact of a uniform object model. If you get lost or confused, you should always remember that message passing is the key: you look for the method in the class of the receiver. This works on any receiver. If the method is not found in the class of the receiver, it is looked up in its superclasses.