Design Pattern

Visitor Pattern

Clean Java-only production-ready implementation.


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.

// ─── 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);
    }
}