SECRET OF CSS

The Power of the Visitor Design Pattern in JavaScript | by jsmanifest | Jun, 2022


Let JavaScript visitors feel at home

thumbnail
Photo by Paras Katwal: https://www.pexels.com/photo/computer-with-code-4218883/

When developing web applications, one powerful strategy that is very rewarding in value is the Visitor Design Pattern. This post will go over the Visitor Pattern in JavaScript and knock away some important concepts and techniques that every JavaScript developer must know when using the Visitor.

In my experience, the Visitor is one of the most complex patterns to understand both in code and in a visual perspective when starting out but is actually not that bad once you get the hang of it.

We mostly find visitors implemented in libraries or frameworks so if you haven’t used many libraries or frameworks you might have not worked with visitors yet. They’re most often useful when library authors seek extensibility.

There are two main participants required to complete the visitor pattern (not including the client code):

  1. The elements that have an accept method (by convention we name the method "accept")
  2. The visitors that define the visit method. This is where they run their logic on elements that they are interested in.

Objects that implement the visit method take the element (or more formally the node) in question as an argument. It is at this time that the visitor can perform their desired logic to objects they are interested in.

If we were authoring a library and we provide a Visitor that will have its visit method called during a traversal (or some looping operation) then we can easily implement some form of extensibility to clients through this call.

For example, let’s say we have this collection of elements (which represent DOM nodes — but are not actual DOM nodes):

Let’s say we were building a tiny, simple JavaScript library that lets consumers of our code provide any collection of elements and give them the ability to easily provide their own functions that can transform their keys/values as they wish.

We can provide an API that iterates through every element in the DOM tree and allow them to pass in their functions as transformers to manipulate nodes they are interested in.

First, we will define our base Node class that takes in the element and stores it internally:

Our Node class defines the accept method that calls visitors via their visit method. Additionally, it passes itself in via this as arguments so visitors are able to freely manipulate the original element.

Next, we will have a base Visitor class that all future visitors will derive from:

With this in place, we are now in the position to start creating visitors.

Let’s start with a SelectOptionsVisitor visitor. This visitor will be interested in select elements and let clients set custom options:

Let’s be the client and create an instance of it. We will make our visitor set all select options to "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" and "Sunday":

Now we need some way for callbacks to land on each element in some loop operation. Libraries such as babel provide a utility named something like traverse to do this.

Let’s demonstrate with our own simple traverser that will iterate each node and call their accept method:

Now, let’s use our new traverse function and pass it our list of elements we had in our first snippet:

Now if we look at our select element, we will notice that it was transformed to include the seven days of the week as options:

power-of-visitor-select-options-node-manipulation.png

If you’re like me, you might have a weird habit of associating different patterns with different fruits. Yes, fruits like apples and bananas. I associate the Visitor pattern with an Apple because just like apples they provide many benefits such as:

  1. Open/Closed Principle — As considered by Robert C. Martin, this principle is “the most important principle of object-oriented design.” The visitor allows developers to introduce new behavior where they can work with different objects of different classes without changing their implementation.
  2. Single Responsibility — You can move multiple versions of the same behavior into the same class. Since visitors implement their logic in a block that can’t be accessed directly from the outside, it’s easy to have them focus on one goal.
  3. When working with various objects, visitors can gather very useful information which can become very convenient when working with complex tree structures. The YAML JavaScript library takes this even further with async support.

Things to watch out for

  • Whenever visitors remove or add new nodes to the tree they must update all visitors otherwise they will cause errors.
  • If the classes that work with the tree don’t implement the accept operations then the visitor will no longer work for them.
  • Most libraries don’t implement these nodes as immutable objects so be aware of making any side effects!
  • The visitor should never be aware of the tree structure of the nodes. Elements should be allowed to call visitors on any of its underlying elements (children for example).

Command design pattern

command-design-pattern-in-javascript

Since visitors can run operations on certain objects they are interested in they can be seen as dispatching “commands” that trigger depending on the object or class.

Composite design pattern

composite-design-pattern-in-javascript

When working with tree structures (like an Abstract Syntax Tree for example) they’re usually implemented as composite structures so that the client code can work with all objects. The visitor pattern shares a similar goal so it is a powerful practice to combine the visitor and composite pattern together.

Iterator design pattern

iterator-design-pattern

Another powerful combination is the iterator and visitor pattern together. With the iterator pattern, there is usually an interface exposed to the client to inject their own algorithms to iterate each object. There are some limitations to this approach like being able to deeply access children of any subtrees.

The visitor pattern can help fill that void with recursion so it is very powerful. Click here to see an example.

I hope you found this to be valuable. Look out for more in the future!



News Credit

%d bloggers like this: