Design Pattern

Visitor Pattern

Clean Java-only production-ready implementation.


What Problem It Solves

You have a stable set of element types (e.g., Book, Electronics, Clothing) but you want to add new operations (e.g., calculateTax(), generateReport(), exportToJson()) without modifying each element class. Visitor lets you define a new operation without changing the elements.

Visitor Trade-offs

ProCon
Adding new operation = new Visitor class (OCP)Adding new element type = update ALL visitors
Related operations grouped in one classVisitor methods break encapsulation (access element internals)
Elements don't need to know about operationsDouble dispatch can be confusing

Implementation

// โ”€โ”€โ”€ EXAMPLE 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// WHAT WE ARE IMPLEMENTING:
// An office suite document exporter formatting texts and diagrams to PDF or
// plain text formats cleanly.
//
// WHERE THE VISITOR FITS IN:
// DocumentElement is the Element interface accepting visitors. TextElement
// represents a Concrete Element. DocumentVisitor acts as the Visitor
// declaring visit().
// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.ArrayList;
import java.util.List;

// --- Visitor interface (one visit method per element type) ---
interface ShoppingCartVisitor {
    double visit(Book book);
    double visit(Electronics electronics);
    double visit(Clothing clothing);
}

// --- Element interface ---
interface ItemElement {
    double accept(ShoppingCartVisitor visitor);
}

// --- Concrete elements (stable types) ---
class Book implements ItemElement {
    private final double price;
    private final String isbn;

    public Book(double price, String isbn) { this.price = price; this.isbn = isbn; }
    public double getPrice() { return price; }
    public String getIsbn() { return isbn; }
    public double accept(ShoppingCartVisitor visitor) { return visitor.visit(this); }
}

class Electronics implements ItemElement {
    private final double price;
    private final String brand;

    public Electronics(double price, String brand) { this.price = price; this.brand = brand; }
    public double getPrice() { return price; }
    public String getBrand() { return brand; }
    public double accept(ShoppingCartVisitor visitor) { return visitor.visit(this); }
}

class Clothing implements ItemElement {
    private final double price;
    private final String size;

    public Clothing(double price, String size) { this.price = price; this.size = size; }
    public double getPrice() { return price; }
    public String getSize() { return size; }
    public double accept(ShoppingCartVisitor visitor) { return visitor.visit(this); }
}

// --- Concrete visitor (one operation) ---
class TaxCalculator implements ShoppingCartVisitor {
    public double visit(Book book) {
        // Books: 5% tax
        double tax = book.getPrice() * 0.05;
        System.out.println("  [Tax] Book ISBN=" + book.getIsbn() + " tax=" + tax);
        return tax;
    }

    public double visit(Electronics electronics) {
        // Electronics: 18% tax (GST)
        double tax = electronics.getPrice() * 0.18;
        System.out.println("  [Tax] Electronics brand=" + electronics.getBrand() + " tax=" + tax);
        return tax;
    }

    public double visit(Clothing clothing) {
        // Clothing: 12% tax
        double tax = clothing.getPrice() * 0.12;
        System.out.println("  [Tax] Clothing size=" + clothing.getSize() + " tax=" + tax);
        return tax;
    }
}

// --- Another visitor (new operation, no element changes needed) ---
class DiscountCalculator implements ShoppingCartVisitor {
    public double visit(Book book) {
        double discount = 0;
        if (book.getPrice() > 500) discount = book.getPrice() * 0.10;
        System.out.println("  [Discount] Book discount=" + discount);
        return discount;
    }

    public double visit(Electronics electronics) {
        double discount = electronics.getPrice() * 0.05;
        System.out.println("  [Discount] Electronics discount=" + discount);
        return discount;
    }

    public double visit(Clothing clothing) {
        double discount = 0;
        if (clothing.getSize().equals("S") || clothing.getSize().equals("XS")) discount = clothing.getPrice() * 0.15;
        System.out.println("  [Discount] Clothing discount=" + discount);
        return discount;
    }
}

public class Main {
    public static void main(String[] args) {
        List<ItemElement> items = new ArrayList<>();
        items.add(new Book(600, "ISBN-1234"));
        items.add(new Electronics(15000, "Samsung"));
        items.add(new Clothing(800, "M"));

        ShoppingCartVisitor taxCalc = new TaxCalculator();
        ShoppingCartVisitor discountCalc = new DiscountCalculator();

        double totalTax = 0, totalDiscount = 0;
        for (ItemElement item : items) {
            totalTax += item.accept(taxCalc);
            totalDiscount += item.accept(discountCalc);
        }

        System.out.println("\nTotal tax: $" + totalTax);
        System.out.println("Total discount: $" + totalDiscount);
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...