Selector Generating Visitor

[PersonalPattern]

(This page assumes knowledge of the VisitorPattern)

Therefore, implement a SelectorGeneratingVisitor. Sample code
 Visitor>visit: anObject
| selector |
selector := self generateSelector: anObject ifNone: [^self error: 'No visit selector'].
^self perform: selector with: anObject
 Visitor>generateSelector: anObject ifNone: aBlock 
| class selector |
class := anObject class.
[class = nil ifTrue: [^aBlock value].
selector := ('visit' , class name , ':') asSymbol.
self respondsTo: selector]
whileFalse: [class := class superclass].
^selector

Create concrete visitor classes by subclassing Visitor and coding methods such as #visitNumber:, #visitString:, and #visitOrderedCollection:


These options require a little more bookkeeping, but compare for clarity and speed:

 Visitor>visit: anObject
| class |
class := anOject class.
class = Integer ifTrue: [^self visitInteger: anObject].
class = Number ifTrue: [^self visitNumber: anObject].
class = String ifTrue:  [^self visitString: anObject].
class = OrderedCollection ifTrue: [^self visitOrderedCollection: anObject].
self error: 'Cannot visit ', class printString
or

 Visitor>visit: anObject
self 
perform: (self visitSelector: anObject)
with: anObject

Visitor>visitSelector: anObject ^self visitDictionary at: anObject class ifAbsent: [#visitError:]

Visitor>visitError: anObject self error: 'Cannot visit ', anObject class printString

Visitor>visitDictionary visitDictionary isNil ifTrue: [visitDictionary := Dictionary new at: Integer put: #visitInteger:; at: Number put: #visitNumber:; at: String put: #visitString:; ...; yourself]. ^visitDictionary


I like 'em (especially #visitError:). They have the progressive flavor of YAGNI and DTST (YouArentGonnaNeedIt and DoTheSimplestThingThatCouldPossiblyWork). If you at some time run into the problem of wanting a visit method to apply to all subclasses, you might (at that time) want to move on to generating the selectors to give you that capability. As in your example, you could dynamically cache the selectors for speed. --StanSilver

 !Visitor methodsFor: 'visit'!

visit: anObject ^self perform: (self visitSelector: anObject class) with: anObject!

visitSelector: aClass ^self selectorDictionary at: aClass ifAbsentPut: [self generateValidSelector: aClass]!

generateValidSelector: aClass | class selector | class := aClass.

[selector := self generateSelector: class. self respondsTo: selector] whileFalse: [class = Object ifTrue: [^#visitError:]. class := class superclass]. ^selector!

generateSelector: aClass ^('visit' , aClass name , ':') asSymbol!

selectorDictionary selectorDictionary isNil ifTrue: [selectorDictionary := Dictionary new]. ^selectorDictionary!

visitError: anObject self error: 'Cannot visit ' , anObject class printString!


This could also be done in Java, using the Reflection library.


EditText of this page (last edited November 13, 2000) or FindPage with title or text search