Selector Generating Visitor

(This page assumes knowledge of the Visitor Pattern)

You want to implement the Visitor Pattern, as described in Design Patterns by the Gang Of Four.

You are working in a language that supports the #perform:
method (Java or Smalltalk).

You can't or don't want to add accept methods to your target classes, and/or

You want some visit methods to apply to a hierarchy of subclasses.

Therefore, implement a Selector Generating Visitor.

When the visitor visits an object, it generates its own visit selector based on the name of the object's class. If the visitor does not implement a method with that selector, it tries a selector based on the name of the next higher superclass, repeating until a valid selector is generated or visitObject:
is reached. The generated selector is then performed with the object as the argument.

This avoids the need to modify target classes, and lets visit methods be inherited by subclasses.

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 (You Arent Gonna Need It and Do The Simplest Thing That Could Possibly Work). 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. --Stan Silver

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

See original on c2.com