Java Visitor Pattern

21 December 2014
By Gonçalo Marques
In this article we will see how to implement the Visitor design pattern in Java.

Visitor Design Pattern

The Visitor Design Pattern goal is to provide a way to clearly separate domain objects from algorithms that operate over them. This way one may change the algorithms themselves without modifying the domain objects or structures.

The are two kinds of participants in the Visitor pattern:

  • Visitor: Will take a domain object - or visitable - and execute operations over it;

  • Visitable: Represents a domain object which will accept a visitor and pass itself into this same visitor in order to allow the latter to perform operations over its data.

Practical example

In this practical example we will define an illustrative document structure, and use a couple of visitors in order to present the document in two distinct representations: HTML and PDF.

A document is defined by the following components:

  • Document
  • Header
  • Content
  • Footer

One could obviously specify the document using a finer level of granularity, such as separating the content in sections, paragraphs, images, etc. but that is out of the scope of this article.

Now that we have all of our document components we may start do define them:

DocumentPart interface

public interface DocumentPart {

  void accept(DocumentVisitor visitor);

}

Our document components will implement a common interface, that we named DocumentPart. By implementing this interface we make sure that every document component will be ready to accept a visitor that will execute operations over it.

Document

public class Document implements DocumentPart {

  private List<DocumentPart> documentParts = new ArrayList<>();

  public String getArticleMetadata() {
    return "Created in 2014";
  }

  public void addDocumentPart(DocumentPart documentPart) {
    documentParts.add(documentPart);
  }

  @Override
  public void accept(DocumentVisitor visitor) {
    visitor.visit(this);
    for (DocumentPart documentPart : documentParts) {
      documentPart.accept(visitor);
    }
  }

}

Header

public class Header implements DocumentPart {

  public String getTitle() {
    return "Document title";
  }

  @Override
  public void accept(DocumentVisitor visitor) {
    visitor.visit(this);
  }

}

Content

public class Content implements DocumentPart {

  public String getDocumentContent() {
    return "Document content";
  }

  @Override
  public void accept(DocumentVisitor visitor) {
    visitor.visit(this);
  }

}

Footer

public class Footer implements DocumentPart {

  public String getFooter() {
    return "Document footer";
  }

  @Override
  public void accept(DocumentVisitor visitor) {
    visitor.visit(this);
  }

}

Note that according to the DocumentPart interface, every document component must implement the accept() method, which means that every component will be ready to accept a visitor. The document components will then pass themselves into the visitor as defined by the Visitor Design Pattern.

Now we define the DocumentVisitor interface:

DocumentVisitor

public interface DocumentVisitor {

  void visit(Document document);

  void visit(Header header);

  void visit(Content content);

  void visit(Footer footer);

}

And an illustrative visitor that generates the document representation in HTML format:

HtmlDocumentVisitor

public class HtmlDocumentVisitor implements DocumentVisitor {

  @Override
  public void visit(Document document) {
    System.out.println("Generating document metadata HTML markup: "
        + document.getDocumentMetadata());
  }

  @Override
  public void visit(Header header) {
    System.out.println("Generating document header HTML markup: "
        + header.getTitle());
  }

  @Override
  public void visit(Content content) {
    System.out.println("Generating document content HTML markup: "
        + content.getDocumentContent());
  }

  @Override
  public void visit(Footer footer) {
    System.out.println("Generating document footer HTML markup: "
        + footer.getFooter());
  }

}

We may now apply the visitor we just defined to the document data structure:

HtmlDocumentVisitor usage

Document document = new Document();
document.addDocumentPart(new Header());
document.addDocumentPart(new Content());
document.addDocumentPart(new Footer());

DocumentVisitor documentVisitor = new HtmlDocumentVisitor();
document.accept(documentVisitor);

Which will generate the following output:


Generating document metadata HTML markup: Created in 2014
Generating document header HTML markup: Document title
Generating document content HTML markup: Document content
Generating document footer HTML markup: Document footer

Now we define another visitor that generates the document representation in PDF format:

PDFDocumentVisitor

public class PDFDocumentVisitor implements DocumentVisitor {

  @Override
  public void visit(Document document) {
    System.out.println("Generating document metadata in PDF format: "
        + document.getDocumentMetadata());
  }

  @Override
  public void visit(Header header) {
    System.out.println("Generating document header in PDF format: "
        + header.getTitle());
  }

  @Override
  public void visit(Content content) {
    System.out.println("Generating document content in PDF format: "
        + content.getDocumentContent());
  }

  @Override
  public void visit(Footer footer) {
    System.out.println("Generating document footer in PDF format: "
        + footer.getFooter());
  }

}

Which would generate the following output if it was applied against the document structure:

PDFDocumentVisitor usage

DocumentVisitor documentVisitor = new PDFDocumentVisitor();
document.accept(documentVisitor);


Generating document metadata in PDF format: Created in 2014
Generating document header in PDF format: Document title
Generating document content in PDF format: Document content
Generating document footer in PDF format: Document footer

Reference

Visitor pattern

Related Articles

Comments

About the author
Gonçalo Marques is a Software Engineer with several years of experience in software development and architecture definition. During this period his main focus was delivering software solutions in banking, telecommunications and governmental areas. He created the Bytes Lounge website with one ultimate goal: share his knowledge with the software development community. His main area of expertise is Java and open source.

GitHub profile: http://github.com/gonmarques

He is also the author of the WiFi File Browser Android application: