TypeScript Visitor Design Pattern
What is visitor design pattern?
The visitor design pattern is a behavioral design pattern that provides the ability to separate algorithms from the objects they are working on.
There are several terminologies in the visitor design pattern. These are:
- Visitor: It is the interface that defines the properties for a concrete visitor and uses the concrete elements for visiting methods.
- Concrete Visitor: It handles the specifications defined in the visitor.
- Element: It is the interface that defines the method for visitors.
- Concrete Element: It implements the interface and calls the proper visitor methods.
- Client: This refers to the application or function that communicates with the system.
When should the visitor design pattern be used?
The visitor design pattern can be used when there is a need for performing operations on elements throughout the entire system.
With the visitor design pattern, the system’s focus can be changed by discarding the auxiliary part from the business logic. This way, the system would be focused on the main part of the structure.
A visitor can also be used when a specific behavior of the system is incompatible within the rest of the system hierarchy.
How to implement visitor design pattern in TypeScript
Let’s apply the Visitor design pattern to TypeScript. First, let’s imagine a scenario where we are trying to add an export feature to our application. Initially, we added the export method directly to the related classes. This worked okay; however, things took a turn for the worse when we attempted to increase our export options.
The system was already handling one export option, and when we tried to add another, in some cases, it would refuse to work, citing that some components were already in use. Due to the bugs and other potential errors, we decided to employ a different strategy — the Visitor design pattern.
We extracted our export logic from the related classes and placed them into visitors. Then, we formed a connection between these visitors and the related classes by adding visitor accept methods. This way, classes now have the ability to delegate the most appropriate way of representing themselves to the visitor classes via the visitor method.
Visitor design pattern diagram
Visitor design pattern code
// Element interface
interface DocumentElement {
accept(visitor: ExportVisitor): void;
}
// Concrete element
class TextElement implements DocumentElement {
private content: string;
constructor(content: string) {
this.content = content;
}
accept(visitor: ExportVisitor): void {
visitor.exportText(this);
}
getContent(): string {
return this.content;
}
}
// Visitor interface
interface ExportVisitor {
exportText(textElement: TextElement): void;
}
// Concrete visitor
class PDFExporter implements ExportVisitor {
exportText(textElement: TextElement): void {
console.log(`Exporting text to PDF: ${textElement.getContent()}`);
}
}
// Concrete visitor
class HTMLExporter implements ExportVisitor {
exportText(textElement: TextElement): void {
console.log(`Exporting text to HTML: ${textElement.getContent()}`);
}
}
// Client
const textElement = new TextElement("This is a text element.");
//Exporting text to PDF: This is a text element.
const pdfExporter = new PDFExporter();
textElement.accept(pdfExporter);
//Exporting text to HTML: This is a text element.
const htmlExporter = new HTMLExporter();
textElement.accept(htmlExporter);