Visitor Pattern

The Visitor Pattern is a behavioral design pattern that allows you to add new operations or behaviors to a group of related objects without modifying their classes.

It achieves this by separating the algorithm from the objects it operates on. The pattern is useful when you have a stable set of classes and want to add new behaviors without altering their code.

Visitor Pattern Real-World Example:

Think of a museum visit as an analogy for the Visitor Pattern. The museum is filled with different exhibits (objects), such as paintings, sculptures, and artifacts. Visitors (the algorithm) want to explore and observe each exhibit in their own way without changing the exhibits themselves.

Visitor Pattern C++ Example:

Let’s demonstrate the Visitor Pattern with a simple example of a shopping cart. We’ll have different types of items in the cart (e.g., books, clothing) and perform different operations on them using a visitor.

Visitor Pattern Explanation of Code:

#include <iostream>
#include <vector>
#include <string>

// Forward declaration
class Book;
class Clothing;

// Visitor Interface
class ShoppingCartVisitor {
public:
    virtual void visit(Book& book) = 0;
    virtual void visit(Clothing& clothing) = 0;
};

// Element Interface
class ShoppingCartItem {
public:
    virtual void accept(ShoppingCartVisitor& visitor) = 0;
};

// Concrete Element: Book
class Book : public ShoppingCartItem {
private:
    std::string title;
    float price;

public:
    Book(const std::string& title, float price) : title(title), price(price) {}

    void accept(ShoppingCartVisitor& visitor) override {
        visitor.visit(*this);
    }

    float getPrice() const {
        return price;
    }
};

// Concrete Element: Clothing
class Clothing : public ShoppingCartItem {
private:
    std::string description;
    float price;

public:
    Clothing(const std::string& description, float price) : description(description), price(price) {}

    void accept(ShoppingCartVisitor& visitor) override {
        visitor.visit(*this);
    }

    float getPrice() const {
        return price;
    }
};

// Concrete Visitor: TaxCalculator
class TaxCalculator : public ShoppingCartVisitor {
public:
    void visit(Book& book) override {
        float taxRate = 0.1f; // 10% tax on books
        float taxAmount = book.getPrice() * taxRate;
        std::cout << "Tax for book '" << book.getTitle() << "': $" << taxAmount << std::endl;
    }

    void visit(Clothing& clothing) override {
        float taxRate = 0.15f; // 15% tax on clothing
        float taxAmount = clothing.getPrice() * taxRate;
        std::cout << "Tax for clothing '" << clothing.getDescription() << "': $" << taxAmount << std::endl;
    }
};

int main() {
    std::vector<ShoppingCartItem*> cart;

    Book book1("The Lord of the Rings", 25.0f);
    Clothing clothing1("T-Shirt", 15.0f);
    Book book2("Harry Potter and the Sorcerer's Stone", 20.0f);

    cart.push_back(&book1);
    cart.push_back(&clothing1);
    cart.push_back(&book2);

    TaxCalculator taxCalculator;

    for (ShoppingCartItem* item : cart) {
        item->accept(taxCalculator);
    }

    return 0;
}

Output:

Tax for book 'The Lord of the Rings': $2.5
Tax for clothing 'T-Shirt': $2.25
Tax for book 'Harry Potter and the Sorcerer's Stone': $2

Visitor Pattern Explanation of the Code:


In this example, we have five main components:

  1. ShoppingCartVisitor is the visitor interface that declares the visit methods for each type of item in the shopping cart.
  2. ShoppingCartItem is the element interface that declares the accept method to accept a visitor.
  3. Book and Clothing are concrete elements that implement ShoppingCartItem and provide specific implementations of the accept method.
  4. TaxCalculator is the concrete visitor that implements the visit methods to calculate taxes for each type of item.

In the main function, we create a shopping cart with different items (books and clothing). We then create a TaxCalculator visitor and use it to calculate taxes for each item in the cart.

Applications of Visitor Pattern:

  1. Document processing: The Visitor Pattern can be used to perform various operations on elements of a document (e.g., extracting data, formatting).
  2. Compiler and interpreter design: In language processing, visitors can traverse the abstract syntax tree and perform different actions on nodes.
  3. Object serialization: It can be used to serialize objects into different formats based on the visitor used.

Visitor Pattern Pros and Cons:

Visitor Pattern Pros:

  • Open for extension, closed for modification: You can add new behaviors (visitors) without modifying existing elements (elements).
  • Separation of concerns: The pattern separates the algorithm (visitor) from the object structure (elements), making it easier to maintain and add new operations.
  • Flexibility: It allows you to perform different operations on elements without changing their classes.

Visitor Pattern Cons:

  • Complexity: The Visitor Pattern can introduce additional complexity, especially for simple structures or when there are many different visitors.
  • Tight coupling with elements: Visitors need access to the specific classes of elements they visit, which can lead to tighter coupling between the visitor and the elements.

Leave a Reply