3.1 Understanding Object-Oriented Programming

But just what is an object?

At its simplest, an object has two components:

The method name is called a selector because it is used to select which behavior is invoked. For example, in 'hello' at: 1 put: $B, the method invoked has the selector #at:put: and the arguments 1 and $B. All selectors are symbols.

Object instances are created – instantiated – following a model or template. This model is known as its Class. All instances of a class share the same methods and so react in the same ways.

For example, there is one class Fraction but many fractions (1/2, 1/3, 23/17, ...) which all behave the way we expect fractions to behave. The class Fraction and the classes it inherits from define this common behavior, as we will now describe.

A given class declares its internal variables – states – and the behavior by implementing the methods. A variable is basically a named box that can hold any object. Each instance variable of a class gets its own box with the common name.

Lets see how the Fraction class is declared:

Number subclass: #Fraction
   instanceVariableNames: 'numerator denominator'
   classVariableNames: ''
   poolDictionaries: ''
   category: 'Kernel-Numbers'

As expected there are two variables – named instance variables – to define the numerator and denominator of a fraction. Each instance of fraction has its own numerator and its own denominator.

From this declaration, we observe there is a hierarchy in the class definition: Fraction is a kind of Number. This means a fraction inherits the internal state (variables) and behavior (methods) defined in the Number class. Fraction is called a subclass of Number, and so naturally we call Number a superclass of Fraction.

A Class specifies the behavior of all of its instances. It is useful to be able to say this object is like that object, but with these differences. We do this in Smalltalk by having classes inherit instance state and behavior from their parent Class. This child, or subclass then specifies just the instance state and behavior that is different from its parent, retaining all the unmodified behaviours.

This aspect of object-oriented programming is called inheritance. In Cuis-Smalltalk, each class inherits from one parent class.

In Smalltalk, we say that each object decides for itself how it responds to a message. This is called polymorphism. The same message selector may be sent to objects of different Classes. The shape (morph) of the computation is different depending on the specific class of the many (poly) possible classes of the object receiving the message.

Different kinds of objects respond to the same #printString message in different, but appropriate ways.

We have already met fractions. Those fractions are objects called instances of the class Fraction. To create an instance we wrote 5 / 4, the mechanism is based on message sending and polymorphism. Let us look into how this works.

The number 5 is an integer receiving the message #/, therefore looking at the method / in the Integer class we can see how the fraction is instantiated. See part of this method:

/ aNumber
"Refer to the comment in Number / "
| quoRem |
aNumber isInteger ifTrue:
../..
   ifFalse: [^ (Fraction numerator: self denominator: aNumber) reduced]].
../..

From this source code, we learn that in some situations, the method returns a fraction, reduced. We can expect that in some other situation, an integer is returned, for example, 6 / 2.

In the example, we observe the message #numerator:denominator: is sent to the class Fraction, such a message refers to a class method understood only by the Fraction class. It is expected such a named method returns an instance of a Fraction.

Try this out in a workspace:

Fraction numerator: 24 denominator: 21
⇒ 24/21

Observe how the resulting fraction is not reduced. Whereas it is reduced when instantiated with the #/ message:

24 / 21
⇒ 8/7

A class method is often used to create a new instance from a class. In Example 4.7, the message #new is sent to the class OrderedCollection to create a new empty collection; new is a class method.

In Example 4.8, the #newFrom: message is sent to the class OrderedCollection to create a new collection filled with elements from the array given in the argument; newFrom: is another class method.

Now observe the hierarchy of the Number class:

Number
   Float
      BoxedFloat64
      SmallFloat64
   Fraction
   Integer
      LargePositiveInteger
         LargeNegativeInteger
      SmallInteger

Float, Integer and Fraction are direct descendants of the Number class. We have already learned about the #squared message sent to integer and fraction instances:

16 squared ⇒ 256
(2 / 3) squared ⇒ 4/9

As the #squared message is sent to Integer and Fraction instances, the associated squared method is called an instance method. This method is defined in both the Number and Fraction classes.

Let’s examine this method in Number:

Number>>squared
"Answer the receiver multiplied by itself."
   ^ self * self

In an instance method source code, self refers to the object itself, here it is the value of the number. The ↑ (also ^) symbol indicates to return the following value self * self. One might pronounce ^ as “return”.

Now let’s examine this same method in Fraction:

Fraction>>squared
   ^ Fraction
      numerator: numerator squared
      denominator: denominator squared

Here a new fraction is instantiated with the original instance numerator and denominator being squared. This alternate squared method, ensures a fraction instance is returned.

When the message #squared is sent to a number, different methods are executed depending on if the number is a fraction or another kind of number. Polymorphism means that the Class of each instance decides how it will respond to a particular message. Here, the Fraction class is overriding the squared method, defined above in the class hierarchy. If a method is not overridden, an inherited method is invoked to respond to the message.

Still in the Number hierarchy, let’s examine another example of polymorphism with the #abs message:

-10 abs ⇒ 10
5.3 abs ⇒ 5.3
(-5 / 3) abs ⇒ 5/3

The implementation in Number does not need much explanation. There is the #ifTrue:ifFalse: we have not yet discussed so far, but the code is quite self-explanatory:

Number>>abs
"Answer a Number that is the absolute value (positive magnitude) of the 
receiver."
   self < 0
      ifTrue: [^ self negated]
      ifFalse: [^ self]

This implementation will do just fine for the Number subclasses. Nevertheless, there are several classes overriding it for specialized or optimized cases.

For example, regarding large positive integer, abs is empty. Indeed, in the absence of explicitly returned value, the default returned value is the instance itself, in our situation the LargePositiveInteger instance:

LargePositiveInteger>>abs

The LargeNegativeInteger knows it is negative and its absolute value is itself but with its sign reversed, that is negated:

LargeNegativeInteger>>abs
   ^ self negated

These two overriding methods are more efficient as they avoid unnecessary checks and ifTrue/ifFalse branches. Polymorphism is often used to avoid unnecessary checks and code branches.

 note If you select the text abs in a Browser or Workspace and right-click to get the context menu, you will find an entry Implementors of it. You can select this or use Ctrl-m (iMplementors) to see how various methods for #abs use polymorphism to specialize their answer to produce the naturally expected result.

As an object instance is modeled by its class, it is possible to ask any object its class with the #class message. Observe carefully the class returned in lines 2 and 3:

1 class ⇒ SmallInteger 
(1/3) class ⇒ Fraction 
(6/2) class ⇒ SmallInteger 
(1/3) asFloat class  ⇒ SmallFloat64 
(1.0/3) class ⇒ SmallFloat64 
'Hello' class ⇒ String 
('Hello' at: 1) class ⇒ Character

Example 3.1: Asking the class of an instance