Polymorphism, Method Lookup & doesNotUnderstand:
Different objects respond to the same message in their own way:
17.1 Polymorphism
Different objects respond to the same message in their own way:
No if on type is needed: send a message and let the receiver decide. Replacing type tests (isKindOf:) with polymorphic messages is a core Smalltalk skill.
{ Rect width: 2 height: 3 . Circle radius: 1 . Square side: 2 }
collect: [ :shape | shape area ]. "→ #(6 3.14159... 4)"
#(1 'one' #one $1 1.0) collect: [ :each | each printString ].17.2 Method lookup
When an object receives a message:
Look for the selector in the method dictionary of the receiver's class.
If not found, look in the superclass, then its superclass, … up to ProtoObject.
If found, execute the method with self bound to the receiver.
If not found anywhere, send doesNotUnderstand: aMessage to the receiver — starting the lookup again. Object>>doesNotUnderstand: raises MessageNotUnderstood, which opens the debugger.
For super, lookup starts in the superclass of the method's defining class — determined at compile time.
The VM speeds this up with inline caches (per send site) and a global method cache — so dynamic dispatch is fast.
aSquare area
Square — no #area
Rect — #area found → execute with self = aSquare17.3 doesNotUnderstand:
The Message object passed in contains the selector and arguments; you can override doesNotUnderstand: to implement proxies, dynamic accessors, or delegation:
Subclassing ProtoObject (which understands very few messages) makes almost every message fall through to doesNotUnderstand:. Use with care: tools may struggle to inspect proxies.
The same technique implements dynamic records: a doesNotUnderstand: that maps unary selectors to dictionary lookups and one-argument keyword selectors to at:put: (always fall back to super doesNotUnderstand: aMessage).
ProtoObject << #LoggingProxy
slots: { #target };
package: 'Demo'
LoggingProxy class >> on: anObject
^ self basicNew setTarget: anObject
LoggingProxy >> setTarget: anObject
target := anObject
LoggingProxy >> doesNotUnderstand: aMessage
Transcript crShow: 'Intercepted: ', aMessage selector printString.
^ aMessage sendTo: target| p |
p := LoggingProxy on: (OrderedCollection new).
p add: 3; add: 4.
p size. "→ 2, and the Transcript shows each intercepted message"17.4 respondsTo: and type tests
Methods like isString are defined in Object (answering false) and overridden in the relevant class (answering true) — itself an application of polymorphism.
x respondsTo: #do:. "duck-typing check — use sparingly"
x isString. x isNumber. x isCollection. x isSymbol. x isBlock.