Java Design Patterns: Visitor and Mediator Patterns

Visitor Pattern

The Visitor Pattern is a behavioral design pattern that allows you to add new operations to existing object structures without modifying the structures themselves. This pattern is particularly useful when you need to perform various operations on a collection of objects that have different interfaces.

The key components of the Visitor Pattern include:

  • Visitor Interface: Declares a set of visit methods for each concrete element class.
  • Concrete Visitor: Implements the visitor interface to provide the actual operations on elements.
  • Element Interface: Declares an accept method that takes a visitor as an argument.
  • Concrete Element: Implements the accept method to call the appropriate visit method.
  • Object Structure: Can enumerate its elements and provide a high-level interface to allow visitors to visit its elements.

Let's consider a practical example where we have a document processing system that can handle different types of elements: text, images, and tables. We want to implement functionality to count the number of each element type.

First, we define the visitor interface and element interfaces:

interface DocumentVisitor {
    void visit(TextElement text);
    void visit(ImageElement image);
    void visit(TableElement table);
}

interface DocumentElement {
    void accept(DocumentVisitor visitor);
}

Next, we implement the concrete elements:

class TextElement implements DocumentElement {
    private String content;
    
    public TextElement(String content) {
        this.content = content;
    }
    
    @Override
    public void accept(DocumentVisitor visitor) {
        visitor.visit(this);
    }
    
    public String getContent() {
        return content;
    }
}

class ImageElement implements DocumentElement {
    private String filename;
    
    public ImageElement(String filename) {
        this.filename = filename;
    }
    
    @Override
    public void accept(DocumentVisitor visitor) {
        visitor.visit(this);
    }
    
    public String getFilename() {
        return filename;
    }
}

class TableElement implements DocumentElement {
    private int rows;
    private int columns;
    
    public TableElement(int rows, int columns) {
        this.rows = rows;
        this.columns = columns;
    }
    
    @Override
    public void accept(DocumentVisitor visitor) {
        visitor.visit(this);
    }
    
    public int getRows() {
        return rows;
    }
    
    public int getColumns() {
        return columns;
    }
}

Now, we implement a concrete visitor that counts the elements:

class ElementCounter implements DocumentVisitor {
    private int textCount = 0;
    private int imageCount = 0;
    private int tableCount = 0;
    
    @Override
    public void visit(TextElement text) {
        textCount++;
    }
    
    @Override
    public void visit(ImageElement image) {
        imageCount++;
    }
    
    @Override
    public void visit(TableElement table) {
        tableCount++;
    }
    
    public void printCounts() {
        System.out.println("Text elements: " + textCount);
        System.out.println("Image elements: " + imageCount);
        System.out.println("Table elements: " + tableCount);
    }
}

Finally, we implement the document structure and test the pattern:

class Document {
    private List<DocumentElement> elements = new ArrayList<>();
    
    public void addElement(DocumentElement element) {
        elements.add(element);
    }
    
    public void process(DocumentVisitor visitor) {
        for (DocumentElement element : elements) {
            element.accept(visitor);
        }
    }
}

public static void main(String[] args) {
    Document document = new Document();
    document.addElement(new TextElement("Introduction"));
    document.addElement(new ImageElement("logo.png"));
    document.addElement(new TableElement(3, 4));
    document.addElement(new TextElement("Conclusion"));
    
    ElementCounter counter = new ElementCounter();
    document.process(counter);
    counter.printCounts();
}

The output of this example would be:

Text elements: 2
Image elements: 1
Table elements: 1

Advantages of Visitor Pattern

  • Extensibility: New operations can be added without modifying the existing element classes.
  • Single Responsibility Principle: Related operations are grouped into a single visitor class.
  • Clean interfaces: Element interfaces are kept clean of implementation-specific operations.

Disadvantages of Visitor Pattern

  • Violation of Open/Closed Principle: Adding new element types requires modifying all existing visitors.
  • Increased complexity: The pattern can make the code more complex and harder to understand.
  • Tight coupling: Concrete visitors are tightly coupled to concrete element types.

When to Use Visitor Pattern

  • When an object structure contains many classes of objects with differing interfaces, and you want to perform operations on these objects.
  • When you want to keep related operations together in one class rather than scattered across multiple classes.
  • When the classes defining the object structure rarely change, but you often want to define new operations over the structure.

Mediator Pattern

The Mediator Pattern is a behavioral design pattern that centralizes complex communications and control between objects. The pattern defines an object that encapsulates how a set of objects interact, promoting loose coupling by keeping objects from referring to each other explicitly.

The key components of the Mediator Pattern include:

  • Mediator Interface: Defines the interface for communication between colleagues.
  • Concrete Mediator: Implements the mediator interface and coordinates communication between colleagues.
  • Colleague Interface: Defines the interface for communication with the mediator.
  • Concrete Colleague: Implements the colleague interface and communicates with the mediator.

Let's consider an example of a chat application where users can send messages to each other through a chat room mediator.

First, we define the mediator and colleague interfaces:

interface ChatMediator {
    void sendMessage(String message, User sender);
    void addUser(User user);
}

interface User {
    void send(String message);
    void receive(String message);
    String getName();
}

Next, we implement the concrete mediator:

class ChatRoom implements ChatMediator {
    private List<User> users = new ArrayList<>();
    
    @Override
    public void sendMessage(String message, User sender) {
        for (User user : users) {
            if (user != sender) {
                user.receive(sender.getName() + ": " + message);
            }
        }
    }
    
    @Override
    public void addUser(User user) {
        users.add(user);
    }
}

Now, we implement the concrete colleague:

class ChatUser implements User {
    private String name;
    private ChatMediator mediator;
    
    public ChatUser(String name, ChatMediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }
    
    @Override
    public void send(String message) {
        mediator.sendMessage(message, this);
    }
    
    @Override
    public void receive(String message) {
        System.out.println(name + " received: " + message);
    }
    
    @Override
    public String getName() {
        return name;
    }
}

Finally, we test the pattern:

public static void main(String[] args) {
    ChatMediator chatRoom = new ChatRoom();
    
    User user1 = new ChatUser("Alice", chatRoom);
    User user2 = new ChatUser("Bob", chatRoom);
    User user3 = new ChatUser("Charlie", chatRoom);
    
    chatRoom.addUser(user1);
    chatRoom.addUser(user2);
    chatRoom.addUser(user3);
    
    user1.send("Hello everyone!");
    user2.send("Hi Alice!");
}

The output of this example would be:

Bob received: Alice: Hello everyone!
Charlie received: Alice: Hello everyone!
Alice received: Bob: Hi Alice!
Charlie received: Bob: Hi Alice!

Advantages of Mediator Pattern

  • Loose coupling: Colleagues don't need to know about each other, reducing dependencies.
  • Centralized control: Complex interactions are managed in one place.
  • Reusability: Mediators can be reused in different contexts.

Disadvantages of Mediator Pattern

  • Complex mediator: The mediator class can become overly complex as it handles all communication.
  • Single point of failure: The mediator becomes a critical component in the system.
  • Performance issues: All communication goes through the mediator, which can become a bottleneck.

When to Use Mediator Pattern

  • When a set of objects communicate in well-defined but complex ways.
  • When you want to customize a system without changing its individual components.
  • When reusing an object is difficult because it refers to and communicates with many other objects.

Tags: java Design Patterns Visitor Pattern Mediator Pattern Behavioral Patterns

Posted on Wed, 15 Jul 2026 17:18:11 +0000 by Pyro4816