Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
157: The Visitor Pattern
I've been working on this document exporting tool for a client, and I've hit a wall. The system has a few different types of content elements—TextElement, ImageElement, and TableElement. Initially, it seemed simple: just give each class a render() method. But then the client asked for an HTML export. Then a PDF export. Then a word-count utility. Then a spell-checker.
The "One More Method" Problem
Here is where I started. I just kept adding methods to the base interface and implementing them in every single subclass. It looked like this:
public interface DocumentElement {
void render();
void exportToHtml();
void exportToPdf();
int countWords();
void checkSpelling();
}
At first, it worked. But look at the ImageElement class. What does countWords() even do for an image? I ended up writing return 0; or throwing an UnsupportedOperationException. I'm polluting my data classes with behavior that doesn't belong there. Every time we add a new "operation" to the document, I have to open every single element file, modify the interface, and implement the logic. It's a maintenance nightmare.
Trying to pull the logic out
I thought, "Why not just use instanceof?" I tried to create a separate ExportService that just checked what it was dealing with. I wrote something like this:
public void export(DocumentElement element) {
if (element instanceof TextElement) {
// handle text
} else if (element instanceof ImageElement) {
// handle image
} // ... and so on
}
I hated it immediately. It's fragile. If I add a VideoElement next week, the compiler won't tell me that I forgot to update the ExportService. I'll just get a silent failure or a generic "not handled" error at runtime. I want the compiler to scream at me if I miss a type.
The Double Dispatch Trick
This is where I stumbled into the Visitor pattern. The goal is to separate the algorithm from the object structure. Instead of the element knowing how to export itself, the element should just know how to "accept" a visitor who does the work.
First, I'll define the Visitor. Notice how the visitor has a specific method for every concrete type in my system:
public interface DocumentVisitor {
void visit(TextElement text);
void visit(ImageElement image);
void visit(TableElement table);
}
Now, here is the "magic" part. I add one single method to my DocumentElement interface. Just one. I'm never touching this interface again for new features.
public interface DocumentElement {
void accept(DocumentVisitor visitor);
}
Now, look at how the implementation looks in the TextElement class. This is called "double dispatch." The element doesn't know what the visitor is doing; it just tells the visitor, "Hey, I'm a TextElement, handle me accordingly."
public class TextElement implements DocumentElement {
public String text = "Hello World";
@Override
public void accept(DocumentVisitor visitor) {
visitor.visit(this); // 'this' is known to be a TextElement here
}
}
Putting it all together
Now, when I want to add a new feature—say, a WordCountVisitor—I don't touch the element classes at all. I just create a new class that implements the visitor interface.
public class WordCountVisitor implements DocumentVisitor {
private int totalWords = 0;
@Override
public void visit(TextElement text) {
totalWords += text.text.split("\\s+").length;
}
@Override
public void visit(ImageElement image) {
// Images don't have words, so we do nothing.
// No more dummy methods inside the ImageElement class!
}
@Override
public void visit(TableElement table) {
// Logic to iterate through table cells and count words
}
public int getTotal() { return totalWords; }
}
I've effectively flipped the dependency. The data classes are now stable, and the operations are flexible. If the client wants a "Markdown Export" tomorrow, I just write a MarkdownVisitor. I don't have to risk breaking the core DocumentElement logic to do it. It's a clean separation of concerns.
📋 Practical Task
Implementing a Syntax Highlighter Visitor for a Code Document
You are building a code editor. You have a document consisting of CodeElement objects. There are three types of elements: KeywordElement (like 'public', 'class'), StringLiteralElement (text inside quotes), and CommentElement (text starting with //).
Your task is to implement the Visitor pattern to "highlight" these elements. Instead of printing, your visitor should append the text to a StringBuilder, wrapping the content in ANSI color codes based on the element type.
- KeywordElement should be wrapped in
\u001B[34m(Blue) and reset with\u001B[0m. - StringLiteralElement should be wrapped in
\u001B[32m(Green) and reset. - CommentElement should be wrapped in
\u001B[33m(Yellow) and reset.
Create the CodeVisitor interface, the CodeElement hierarchy with accept methods, and a SyntaxHighlightVisitor class that performs the coloring. Finally, demonstrate it by processing a List<CodeElement>.
There are no comments for now.