Selector Generating Visitor

last modified: November 13, 2000

[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.


Loading...