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: aString32.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.