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.
If you select the text
abs
in a Browser or Workspace and right-click to get the context menu, you will find an entryImplementors 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