Java Visitor Pattern
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:
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.
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); } } }
public class Header implements DocumentPart { public String getTitle() { return "Document title"; } @Override public void accept(DocumentVisitor visitor) { visitor.visit(this); } }
public class Content implements DocumentPart { public String getDocumentContent() { return "Document content"; } @Override public void accept(DocumentVisitor visitor) { visitor.visit(this); } }
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:
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:
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:
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:
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:
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