Tech Blog

Visitor Design Pattern using SQL Statement Builder as an Example

Today I am going to talk about one of our most frequently used design patterns – the Visitor Pattern. In this article I am describing the pattern and showing you a real-life example of the Visitor implemented in one of Exasol’s open-source projects.

What do design patterns mean and why should I know about them?

Design patterns are ready-to-be-used solutions or design decisions for some common software problems. There is no reason to reinvent the wheel each time you encounter a problem while writing software. Sometimes it’s enough just to apply an existing design pattern. Also, developers use patterns to help other developers read the code because the patterns are easily recognizable once you learned them. But you need to be able to recognize a situation in which one or the other design pattern fits, so you should get to know them in advance.

What’s the Visitor Design Pattern?

We use the Visitor Pattern [Gamma et al., p. 331] when we want to separate behavior logic from the objects on which that logic applies. This pattern is applicable when we are not going to modify the structure of the objects too often, but want to add and modify behavior logic. Also the visitor is typically used if you need to walk hierarchical structures.

Let’s first see an example of the Visitor implementation in the SQL Statement Builder.

What’s the SQL Statement Builder?

The Exasol SQL Statement Builder is an open-source project developed and maintained by the Exasol Integration Team. The Exasol SQL Statement Builder abstracts programmatic creation of SQL statements and is intended to replace ubiquitous string concatenation solutions which make the code hard to read and are prone to error and security risks.

The Visitor Pattern in the context of the SQL Statement Builder.

The Exasol SQL Builder is written in Loading...Java and represents different SQL statements in an object-oriented format. That means that we have, for example, high-level abstractions such as `Select` and `Insert` classes representing, respectively, `SELECT` and `INSERT` statements in SQL. And we also have a lot of lower-level abstractions such as a `FromClause`, a `WhereClause`, or a `Column`.

We will use a `Column` object as an example. This object can be used in a SELECT, INSERT, DROP TABLE, and other statements. But we treat a table reference differently in SELECT and DROP TABLE statements, for example. A table reference in a SELECT statement can contain an AS alias, but DROP TABLE statements never contain it:

SELECT * FROM table AS t;DROP TABLE my_table;

We don’t want to store the string rendering logic for each statement inside the `Column`, so we use the Visitor Pattern instead. The visitor pattern consists of two parts:

  1. A visitor class that visits different types of objects.
  2. An `accept` method inside the visited objects that accepts a certain type of visitor.

The visitor pattern is built on a double dispatch principle which means the behavior of the program depends on the types of two components involved in the call: a visited object and a visitor.

You can have more than one visitor for an object, but then the object has to implement multiple `accept` methods. Here is our `Table` class that has five accept methods:

public class Table extends AbstractFragment {   private final String name;   private final String alias;  // Skipping other methods   public void accept(final CreateTableVisitor visitor) {       visitor.visit(this);   }   public void accept(final DropTableVisitor visitor) {       visitor.visit(this);   }   public void accept(final MergeVisitor visitor) {       visitor.visit(this);   }   public void accept(final InsertVisitor visitor) {       visitor.visit(this);   }   public void accept(final SelectVisitor visitor) {       visitor.visit(this);   }}

As you can see, we don’t add any rendering logic here. We just invoke the visitor’s `visit` method, passing the current `Table` instance to it as an argument.  And now let’s check one of the visitors –  `SelectVisitor`. In our case we started the visitor’s implementation with an interface:

public interface SelectVisitor{   public void visit(Select select);   public void visit(FromClause fromClause);   public void visit(WhereClause whereClause);   public void visit(Table table);   // Skipping other methods}

This visitor is going to render a string for a SELECT SQL statement. So it contains `visit` methods for all the components of the SELECT statement and the `Table` is one of them.

And now let’s check the implementation of the `visit` method for a `Table` object:

public class SelectRenderer extends AbstractFragmentRenderer implements SelectVisitor {   @Override   public void visit(final Table table) {       appendCommaWhenNeeded(table);       appendAutoQuoted(table.getName());       if (table.hasAlias()) {           appendKeyWord(" AS ");           append(table.getAlias());       }       setLastVisited(table);   }    // Skipping other methods}

This is the logic that renders our model to a String. The `SelectRenderer` contains all we need for creating a SELECT SQL statement. It is very convenient to have it all in one place. Can you imagine how hard it would be to maintain and modify it if it would be split across a lot of classes?

We also can check another visitor class that visits the same `Table`:

public class DropTableRenderer extends AbstractFragmentRenderer implements DropTableVisitor {   @Override   public void visit(final Table table) {       appendAutoQuoted(table.getName());       setLastVisited(table);   }    // Skipping other methods}

This visitor doesn’t care about an alias of the table, so the implementation is much shorter. Each visitor can have a unique implementation for the same object, but sometimes you want to have the same implementation, in which case you can’t reuse the code of the visitor of the other type. This code duplication is one of the cons of using the visitor pattern. 

Let’s also discuss other pros and cons.

By the way, you can find the original code in the Exasol SQL Statement Builder repository.

Pros and Cons of the Visitor Pattern

+ Gather related logic in one place instead of adding it here and there to classes.

+ Have multiple implementations for some behavior without changing objects.

+ Easy to add new objects without touching existing ones.

– Code duplication in case of similar behavior of different visitors.

– Cyclic dependencies: visitors know about objects and objects know about visitors.

– Have to update all connected visitor classes if an object was changed.


Thanks for reading and let me know which design pattern we should describe next time!

Bibliography:

1. Design Patterns: Elements Of Reusable Object Oriented Software With Applying Uml And Patterns: An Introduction To Object Oriented Analysis And Design And The Unified Process. ISBN-13: 9780582844421. ISBN-10: 0582844428. Authors: Gamma; Larman. Publisher: Addison Wesley. Published: March 2009.

exa-Anastasiia