I meet Visitor pattern

Chanmann Lim
lchanmann
Published in
4 min readJun 1, 2018

My first encounter with Visitor pattern dated back 8 years ago when I was working on Resource Map and we needed to give our users the ability to update and retrieve data via text SMS through the platform.

Resource Map uses treetop, a powerful syntactic analyzer library, to define allowable SMS commands using Parsing expression grammar (PEG) which freed us from writing the parser code manually because treetop would automatically generate one for us based on the our grammar file. However, for that same reason we also could not and should not modified the generated code to plug our new functionalities for updating and retrieving data as it would hurt the maintainability of the module.

In a pairng session, I and Ary, a friend, a mentor and code wizard 🎩 who went on to create the amazing Crystal programming language, decided to refactor the new functionalities to Visitor pattern such that changing the commands grammar would no longer affect the logic for data update and retrieval.

Recently, I came across a toy problem of creating a simple message responder called “Bob bot” which could respond as follow:

  • Bob bot answers ‘Sure.’ if you ask him a question.
  • He answers ‘Woah, chill out!’ if you yell at him (ALL CAPS).
  • He says ‘Fine. Be that way!’ if you address him without actually saying anything.
  • He answers ‘Whatever.’ to anything else.

This is a simple evaluation problem BUT there is a constraint which is you cannot use if, unless or case in your response code.

My mind whispered “use lo.....op”. We can indeed replace the conditional expression if we can make conditions enumerable thus changing conditional to iteration. The conditions for the problem being if a message is a Question, a Yell, a Silence or Anything else. Let’s call them message types. When receiving a message we could iterate through all message types, check if the message is a valid message of that type and return a response accordingly.

But… why message type should be responsible for responding task? What if we need to add another bot say AliceBot which responds differently from BobBot for each type of message. Dang💥, this design doesn’t work!

Wait… what we need here is a way to separate the message types from the algorithms applied to them and allow both entities to evolve separately so that making changes in future in regard to adding new bot would be a breeze. Visitor pattern comes to a rescue.

Visitor lets you define a new operation without changing the classes of the elements on which it operates.

In fact, BobBot itself should know how to respond to each type of message and it should not be the responsibility of message types. Let’s refactor to Visitor pattern.

Meet the Visitor!

Visitor class

Visitor class defines methods to be implemented by its sub-class (i.e. respond_to_question, respond_to_yell, respond_to_silence, respond_to_anything), an initializer, a default instance helper and the respond method that enumerates through all message types and let the first valid message type instance accept the visitor and return the result. If none of the message types are valid for processing it will raise RuntimeError.

Each message type must respond to valid? to check if a passed-in message is legit for the type and accept(visitor) which delegate back to visitor with proper method call to one of the methods to be implemented by the its sub-class as mentioned above. For example, if the message is a Question it should call visitor.respond_to_question.

BobBot inherits Visitor class and overrides all the methods to be implemented defined in the Visitor class.

BobBot inherits Visitor

Alice Bot? The implementation of AliceBot is as trivial as BobBot.

AliceBot also inherits Visitor

Now, the message types: we define a Base type to include common field and initializer. Our message types of interest i.e. Question, Yell ,Silence and Anything should override valid? and accept(visitor) as needed.

Test it out:

bob = BobBot.default
bob.respond('question?') # "Sure."
bob.respond('HEY') # "Woah, chill out!"
bob.respond('') # "Fine. Be that way!"
bob.respond('anything') # "Whatever."
bob.respond(nil) # RuntimeError (Can't respond to: nil)
alice = AliceBot.default
alice.respond('question?') # "Yeah, I'm listening."
alice.respond('HEY') # "🤐"
alice.respond('') # "I can't hear you."
alice.respond('anything') # "No problem."
alice.respond(nil) # RuntimeError (Can't respond to: nil)

👍 Visitor pattern, we crossed path once more. You reminded me of the good memory I had with the lads at InSTEDD iLab. I’m always grateful to know each and everyone of you at InSTEDD and proud to have you all be a part of the treasure in my memory.

Ary’s juggling 🤹‍♂ ️during pomodoro break
InSTEDD iLab’s retreat Jump

I hope you enjoy the article and find good use of Visitor pattern in your own project. Please don’t hesitate to reach out if you have any question for me. Please 👏👏👏 and share if you like or find it useful.

--

--

Chanmann Lim
lchanmann

M.S. in Computer Science, University of Missouri-Columbia.