Design Patterns in Smalltalk

Many *Design Patterns* (Gamma, Helm, Johnson, Vlissides, 1994) came from Smalltalk practice; in Smalltalk, several of them become trivial thanks to blocks, polymorphism, and reflection.

Many *Design Patterns* (Gamma, Helm, Johnson, Vlissides, 1994) came from Smalltalk practice; in Smalltalk, several of them become trivial thanks to blocks, polymorphism, and reflection. Kent Beck's *Smalltalk Best Practice Patterns* captures the idioms at the method level.

32.1 Patterns that become idioms

do:, collect:, select: — built in A block: [ account deposit: 10 ] (or a command object for undo)

A block or any polymorphic object: sortBlock: Superclass method + subclassResponsibility hooks

Class-side instance creation: Shape fromString: 'circle' Class-side instance variable + uniqueInstance (§15.4)

Announcements (Chapter 33) or changed:/update: Proxy / Decorator doesNotUnderstand: forwarding (§17.3)

Cascades: builder name: 'x'; size: 3; build A class answering harmless defaults instead of nil checks Abstract Factory, Prototype

Classes are objects — pass a class as a parameter; copy/postCopy

32.2 Double dispatch

To act on the types of two objects, let each one dispatch in turn — this is how Smalltalk implements mixed arithmetic:

(In real code each method has its own line break after the selector; they are condensed here.)

"Collision handling between asteroids and ships"
Asteroid >> collideWith: anObject
    ^ anObject collideWithAsteroid: self

Ship >> collideWith: anObject
    ^ anObject collideWithShip: self

Asteroid >> collideWithAsteroid: anAsteroid   ^ 'asteroids bounce'
Asteroid >> collideWithShip: aShip            ^ 'ship destroyed'
Ship >> collideWithAsteroid: anAsteroid       ^ 'ship destroyed'
Ship >> collideWithShip: aShip                ^ 'ships dock'

32.3 Visitor

A Printer visitor with the same two methods returning strings adds pretty-printing without touching the node classes. (Accessors value, left, right and constructors omitted for brevity.) Pharo's compiler uses visitors over its AST (RBProgramNodeVisitor).

"Expression tree"
Object << #Expr slots: {}; package: 'Calc'.
Expr << #Num slots: { #value }; package: 'Calc'.
Expr << #Add slots: { #left . #right }; package: 'Calc'.

Num >> accept: aVisitor
    ^ aVisitor visitNum: self
Add >> accept: aVisitor
    ^ aVisitor visitAdd: self

Object << #Evaluator slots: {}; package: 'Calc'.
Evaluator >> visitNum: aNum
    ^ aNum value
Evaluator >> visitAdd: anAdd
    ^ (anAdd left accept: self) + (anAdd right accept: self)

32.4 Null Object

Instead of logger ifNotNil: [ logger log: 'x' ] everywhere, initialize logger := NullLogger new.

Object << #NullLogger slots: {}; package: 'Logging'.
NullLogger >> log: aString
    "do nothing"

Object << #TranscriptLogger slots: {}; package: 'Logging'.
TranscriptLogger >> log: aString
    Transcript crShow: aString

32.5 State and Composite

State: an object delegates to a state object (state := state openFor: self) whose class decides the behavior, replacing conditionals on a status flag. Composite: a composite and its leaves share a protocol (area, draw:, do:); the composite forwards to children with inject:into: or do:. Morphic (morphs containing submorphs) and Spec presenters are large-scale composites.