Visitor
The Visitor pattern allows you to add new operations to a hierarchy of objects without modifying the objects themselves. This is achieved by encapsulating the operations in separate “visitor” classes, which can then traverse the object hierarchy and apply their specific logic to each element. It promotes the Open/Closed Principle by allowing extension without modification of the core data structures.
Usage
The Visitor pattern is frequently used when:
- You need to perform many different, unrelated operations on a complex object structure.
- The object structure is relatively stable, but the operations you need to perform are likely to change.
- You want to avoid “bloating” the element classes with numerous operation methods.
- You need to group related operations together.
Examples
-
Compilers: In a compiler, a visitor can be used to perform different passes over the abstract syntax tree (AST). For example, one visitor might check for type errors, another might generate intermediate code, and a third might optimize the code. The AST nodes themselves (e.g.,
Expression,Statement,Identifier) remain unchanged when new analysis or code generation phases are added. -
XML Processing: Libraries that process XML or similar document structures often use the Visitor pattern. A visitor can represent an action to be performed on each node type in the document (e.g., printing the node’s value, validating its attributes, transforming it to a different format). The XML node classes (e.g.,
Element,Attribute,Text) aren’t modified when a new processing rule is introduced. The visitor’svisit()methods handle the specifics of each node type.
Specimens
15 implementationsThe Visitor pattern allows you to define a new operation without modifying the classes of the objects on which it operates. It’s achieved by moving the operation to a separate “visitor” class that has a visit method for each type of object it can handle. This promotes the Open/Closed Principle.
This Dart implementation defines a basic shape hierarchy (Circle, Square) and a Shape interface. The Visitor interface declares a visit method for each Shape type. ConcreteVisitor implements the Visitor to perform specific operations (e.g., calculating area, printing details). The accept method in each Shape takes a Visitor and calls the appropriate visit method on it, enabling the visitor to operate on the shape. This is idiomatic Dart due to its use of interfaces and method overriding, aligning with its object-oriented nature.
// Define the element interface
abstract class Shape {
void accept(Visitor visitor);
}
// Concrete elements
class Circle implements Shape {
final double radius;
Circle(this.radius);
@override
void accept(Visitor visitor) {
visitor.visitCircle(this);
}
}
class Square implements Shape {
final double side;
Square(this.side);
@override
void accept(Visitor visitor) {
visitor.visitSquare(this);
}
}
// Define the visitor interface
abstract class Visitor {
void visitCircle(Circle circle);
void visitSquare(Square square);
}
// Concrete visitor: Area calculation
class AreaVisitor implements Visitor {
double totalArea = 0;
@override
void visitCircle(Circle circle) {
totalArea += 3.14159 * circle.radius * circle.radius;
}
@override
void visitSquare(Square square) {
totalArea += square.side * square.side;
}
}
// Concrete visitor: Printing details
class PrintVisitor implements Visitor {
@override
void visitCircle(Circle circle) {
print('Circle with radius: ${circle.radius}');
}
@override
void visitSquare(Square square) {
print('Square with side: ${square.side}');
}
}
void main() {
final circle = Circle(5.0);
final square = Square(4.0);
// Calculate area
final areaVisitor = AreaVisitor();
circle.accept(areaVisitor);
square.accept(areaVisitor);
print('Total area: ${areaVisitor.totalArea}');
// Print details
final printVisitor = PrintVisitor();
circle.accept(printVisitor);
square.accept(printVisitor);
}
The Visitor pattern allows you to define new operations on a hierarchy of objects without changing the classes of those objects. It’s achieved by moving the operation’s logic into a separate “visitor” class that knows how to handle each concrete element in the hierarchy. This promotes the Open/Closed Principle.
The Scala implementation uses algebraic data types (ADTs) to represent the element hierarchy and traits to define the visitor interface. Each concrete visitor implements the trait, providing specific visit methods for each element type. Pattern matching within the visit methods handles the element-specific logic. This approach is idiomatic Scala due to its strong support for ADTs and traits, enabling concise and type-safe visitor implementations. The accept method on each element type dispatches to the appropriate visitor method.
// Element interface
sealed trait Expression {
def accept(visitor: ExpressionVisitor): Int
}
// Concrete elements
case class Number(value: Int) extends Expression {
override def accept(visitor: ExpressionVisitor): Int = visitor.visit(this)
}
case class Add(left: Expression, right: Expression) extends Expression {
override def accept(visitor: ExpressionVisitor): Int = visitor.visit(this)
}
case class Multiply(left: Expression, right: Expression) extends Expression {
override def accept(visitor: ExpressionVisitor): Int = visitor.visit(this)
}
// Visitor interface
trait ExpressionVisitor {
def visit(number: Number): Int
def visit(add: Add): Int
def visit(multiply: Multiply): Int
}
// Concrete visitor: Evaluator
class Evaluator extends ExpressionVisitor {
override def visit(number: Number): Int = number.value
override def visit(add: Add): Int = add.left.accept(this) + add.right.accept(this)
override def visit(multiply: Multiply): Int = multiply.left.accept(this) * multiply.right.accept(this)
}
// Concrete visitor: Printer
class Printer extends ExpressionVisitor {
override def visit(number: Number): Int = println(s"Number: ${number.value}").toInt
override def visit(add: Add): Int = println("Add").toInt
override def visit(multiply: Multiply): Int = println("Multiply").toInt
}
// Example usage
object Main {
def main(args: Array[String]): Unit = {
val expression = Add(Number(5), Multiply(Number(2), Number(3)))
val evaluator = new Evaluator()
val result = expression.accept(evaluator)
println(s"Result of evaluation: $result") // Output: 11
val printer = new Printer()
expression.accept(printer) // Output: Add\nMultiply\nNumber: 2\nNumber: 3\nNumber: 5
}
}
The Visitor pattern allows you to define a new operation without modifying the classes of the objects on which it operates. It’s achieved by moving the operation to a separate “visitor” class that accepts different object types as input. This promotes the Open/Closed Principle.
The code defines a Component interface (representing elements in a structure) and concrete components like Text and Image. A Visitor interface declares visit methods for each component type. Concrete visitors, like MarkdownVisitor, implement these methods to perform specific operations. The Client code accepts a visitor and calls the accept method on each component, which then delegates to the appropriate visit method in the visitor. This is idiomatic PHP due to its interface-based approach and reliance on polymorphism.
<?php
interface Component {
public function accept(Visitor $visitor);
}
class Text implements Component {
private string $content;
public function __construct(string $content) {
$this->content = $content;
}
public function getContent(): string {
return $this->content;
}
public function accept(Visitor $visitor): void {
$visitor->visitText($this);
}
}
class Image implements Component {
private string $url;
public function __construct(string $url) {
$this->url = $url;
}
public function getUrl(): string {
return $this->url;
}
public function accept(Visitor $visitor): void {
$visitor->visitImage($this);
}
}
interface Visitor {
public function visitText(Text $text): string;
public function visitImage(Image $image): string;
}
class MarkdownVisitor implements Visitor {
public function visitText(Text $text): string {
return "**" . $text->getContent() . "**";
}
public function visitImage(Image $image): string {
return " . ")";
}
}
class HtmlVisitor implements Visitor {
public function visitText(Text $text): string {
return "<p>" . $text->getContent() . "</p>";
}
public function visitImage(Image $image): string {
return "<img src='" . $image->getUrl() . "' alt='Image'>";
}
}
// Client code
class Document {
private array $components = [];
public function addComponent(Component $component): void {
$this->components[] = $component;
}
public function render(Visitor $visitor): string {
$result = "";
foreach ($this->components as $component) {
$result .= $component->accept($visitor) . "\n";
}
return $result;
}
}
$document = new Document();
$document->addComponent(new Text("Hello, world!"));
$document->addComponent(new Image("https://example.com/image.jpg"));
$markdownVisitor = new MarkdownVisitor();
echo "Markdown:\n" . $document->render($markdownVisitor) . "\n";
$htmlVisitor = new HtmlVisitor();
echo "HTML:\n" . $document->render($htmlVisitor) . "\n";
?>
The Visitor pattern allows you to add new operations to a hierarchy of objects without modifying the objects themselves. It achieves this by defining a separate “visitor” class that implements the operation for each type of object in the hierarchy. The objects “accept” the visitor, allowing the visitor to perform its operation on them.
This Ruby implementation uses a simple Element hierarchy (Node, Leaf) and a Visitor interface with visit_node and visit_leaf methods. Each element type has an accept method that takes a visitor and calls the appropriate visit_ method on it. The ConcreteVisitor performs a specific operation (in this case, counting the number of nodes and leaves) and demonstrates how new operations can be added without altering the element classes. Ruby’s dynamic dispatch and duck typing make the Visitor pattern a natural fit, avoiding the need for explicit interface declarations.
# Element interface
module Element
def accept(visitor)
raise NotImplementedError, "#{self.class} must implement #accept"
end
end
# Concrete Element: Node
class Node
include Element
attr_reader :name, :children
def initialize(name, children = [])
@name = name
@children = children
end
def accept(visitor)
visitor.visit_node(self)
@children.each { |child| child.accept(visitor) }
end
end
# Concrete Element: Leaf
class Leaf
include Element
attr_reader :value
def initialize(value)
@value = value
end
def accept(visitor)
visitor.visit_leaf(self)
end
end
# Visitor interface
module Visitor
def visit_node(node)
raise NotImplementedError, "#{self.class} must implement #visit_node"
end
def visit_leaf(leaf)
raise NotImplementedError, "#{self.class} must implement #visit_leaf"
end
end
# Concrete Visitor: Counter
class Counter
include Visitor
attr_reader :node_count, :leaf_count
def initialize
@node_count = 0
@leaf_count = 0
end
def visit_node(node)
@node_count += 1
end
def visit_leaf(leaf)
@leaf_count += 1
end
end
# Usage example
node1 = Node.new("A", [Leaf.new(1), Node.new("B", [Leaf.new(2)])])
node2 = Node.new("C", [Leaf.new(3)])
counter = Counter.new
node1.accept(counter)
node2.accept(counter)
puts "Nodes: #{counter.node_count}"
puts "Leaves: #{counter.leaf_count}"
The Visitor pattern allows you to define a new operation without modifying the classes of the objects on which it operates. It’s achieved by moving the operation to a separate “visitor” class that accepts different object types as arguments. This is useful when you have a complex object structure and want to add operations that depend on the specific types within that structure.
This Swift implementation defines a Vehicle protocol with concrete types Car and Bike. The VehicleVisitor protocol declares visit methods for each vehicle type. CarVisitor and BikeVisitor are concrete visitors implementing specific actions (e.g., calculating cost). The client code accepts a visitor and each vehicle, calling the appropriate visit method on the visitor. This adheres to Swift’s protocol-oriented programming style, leveraging protocols for defining the interface and concrete types for implementation.
// Vehicle Protocol
protocol Vehicle {
func accept(visitor: VehicleVisitor)
}
// Concrete Vehicles
class Car: Vehicle {
let model: String
init(model: String) {
self.model = model
}
func accept(visitor: VehicleVisitor) {
visitor.visit(car: self)
}
}
class Bike: Vehicle {
let style: String
init(style: String) {
self.style = style
}
func accept(visitor: VehicleVisitor) {
visitor.visit(bike: self)
}
}
// Visitor Protocol
protocol VehicleVisitor {
func visit(car: Car)
func visit(bike: Bike)
}
// Concrete Visitors
class CarCostVisitor: VehicleVisitor {
var cost: Int = 0
func visit(car: Car) {
cost += 100 + (car.model.count * 10)
}
func visit(bike: Bike) {
// Do nothing for bikes in this visitor
}
}
class BikeCostVisitor: VehicleVisitor {
var cost: Int = 0
func visit(car: Car) {
// Do nothing for cars in this visitor
}
func visit(bike: Bike) {
cost += 50 + (bike.style.count * 5)
}
}
// Client Code
let car = Car(model: "Tesla Model S")
let bike = Bike(style: "Mountain Bike")
let carCostVisitor = CarCostVisitor()
car.accept(visitor: carCostVisitor)
print("Car cost: \(carCostVisitor.cost)")
let bikeCostVisitor = BikeCostVisitor()
bike.accept(visitor: bikeCostVisitor)
print("Bike cost: \(bikeCostVisitor.cost)")
The Visitor pattern allows you to define a new operation without changing the classes of the objects on which it operates. It’s achieved by moving the operation to a separate “visitor” class that accepts different object types as input. This is useful when you have a complex object structure and want to add functionality that depends on the specific types within that structure without modifying those types.
Here, we define a simple expression hierarchy (Expression, Number, Addition) and a Visitor interface with a visit method for each expression type. ExpressionEvaluator is a concrete visitor that evaluates the expression. The accept method in each expression class handles calling the correct visit method on the visitor, ensuring type-safe operation execution. This approach is idiomatic Kotlin due to its use of interfaces, sealed classes for a defined hierarchy, and extension functions for the visitor’s operations.
// Expression hierarchy
sealed class Expression {
abstract fun accept(visitor: Visitor)
}
data class Number(val value: Int) : Expression() {
override fun accept(visitor: Visitor) {
visitor.visit(this)
}
}
data class Addition(val left: Expression, val right: Expression) : Expression() {
override fun accept(visitor: Visitor) {
visitor.visit(this)
}
}
// Visitor interface
interface Visitor {
fun visit(number: Number)
fun visit(addition: Addition)
}
// Concrete visitor: Expression Evaluator
class ExpressionEvaluator : Visitor {
fun evaluate(expression: Expression): Int {
return expression.accept(this)
}
override fun visit(number: Number) = number.value
override fun visit(addition: Addition) =
visit(addition.left) + visit(addition.right)
}
// Example Usage
fun main() {
val expression = Addition(Number(5), Addition(Number(2), Number(3)))
val evaluator = ExpressionEvaluator()
val result = evaluator.evaluate(expression)
println("Result: $result") // Output: Result: 10
}
The Visitor pattern allows you to define a new operation without modifying the classes on which it operates. It’s achieved by moving the operation’s logic into a separate “visitor” object, which then traverses the object structure and applies the operation to each element. This is particularly useful when you have a complex object structure and want to add functionality that depends on the specific types within that structure without cluttering those types with conditional logic.
This Rust example defines an Expr enum representing arithmetic expressions (numbers and operations). The Visitor trait defines a method for each Expr variant. Concrete visitors, like EvalVisitor, implement the trait to perform specific actions (like evaluating the expression). The accept method on Expr dispatches to the appropriate visitor method based on the expression’s type. This implementation leverages Rust’s traits and enums for a type-safe and extensible solution, fitting the language’s focus on correctness and composability.
// Define the Element interface
trait ExprVisitor {
fn visit_number(&mut self, number: i32);
fn visit_add(&mut self, left: &Expr, right: &Expr);
fn visit_multiply(&mut self, left: &Expr, right: &Expr);
}
// Define the Element hierarchy
enum Expr {
Number(i32),
Add(Box<Expr>, Box<Expr>),
Multiply(Box<Expr>, Box<Expr>),
}
// Implement the Accept method
impl Expr {
fn accept(&self, visitor: &mut dyn ExprVisitor) {
match self {
Expr::Number(number) => visitor.visit_number(*number),
Expr::Add(left, right) => {
visitor.visit_add(left, right);
}
Expr::Multiply(left, right) => {
visitor.visit_multiply(left, right);
}
}
}
}
// Concrete Visitor: Evaluates the expression
struct EvalVisitor {
result: i32,
}
impl EvalVisitor {
fn new() -> Self {
EvalVisitor { result: 0 }
}
}
impl ExprVisitor {
fn visit_number(&mut self, number: i32) {
self.result = number;
}
fn visit_add(&mut self, left: &Expr, right: &Expr) {
let mut left_visitor = EvalVisitor::new();
left.accept(&mut left_visitor);
let mut right_visitor = EvalVisitor::new();
right.accept(&mut right_visitor);
self.result = left_visitor.result + right_visitor.result;
}
fn visit_multiply(&mut self, left: &Expr, right: &Expr) {
let mut left_visitor = EvalVisitor::new();
left.accept(&mut left_visitor);
let mut right_visitor = EvalVisitor::new();
right.accept(&mut right_visitor);
self.result = left_visitor.result * right_visitor.result;
}
}
fn main() {
let expression = Expr::Add(
Box::new(Expr::Number(5)),
Box::new(Expr::Multiply(Box::new(Expr::Number(2)), Box::new(Expr::Number(3)))),
);
let mut evaluator = EvalVisitor::new();
expression.accept(&mut evaluator);
println!("Result: {}", evaluator.result); // Output: Result: 11
}
The Visitor pattern allows you to define a new operation without changing the classes of the objects on which it operates. It’s achieved by moving the operation to a separate “visitor” class that accepts the object as a parameter. This is useful when you have a complex object structure and want to perform many different operations on it without cluttering the object classes themselves.
This Go implementation defines an Element interface representing the objects to be visited. Concrete elements like ConcreteElementA and ConcreteElementB implement this interface. The Visitor interface declares Visit methods for each concrete element type. ConcreteVisitor implements these methods to perform specific operations. The Accept method on each element takes a visitor and calls the appropriate Visit method. This approach leverages Go’s interfaces and method sets for a clean and type-safe implementation, fitting the language’s emphasis on composition and explicit interfaces.
// element.go
package main
import "fmt"
// Element is the interface for objects that can be visited.
type Element interface {
Accept(Visitor)
}
// ConcreteElementA is a concrete element.
type ConcreteElementA struct {
Data string
}
// Accept visits the concrete element A.
func (a *ConcreteElementA) Accept(v Visitor) {
v.VisitA(a)
}
// ConcreteElementB is a concrete element.
type ConcreteElementB struct {
Value int
}
// Accept visits the concrete element B.
func (b *ConcreteElementB) Accept(v Visitor) {
v.VisitB(b)
}
// Visitor is the interface for visitors.
type Visitor interface {
VisitA(a *ConcreteElementA)
VisitB(b *ConcreteElementB)
}
// ConcreteVisitor is a concrete visitor.
type ConcreteVisitor struct{}
// VisitA performs an operation on ConcreteElementA.
func (v *ConcreteVisitor) VisitA(a *ConcreteElementA) {
fmt.Printf("Visiting ConcreteElementA with data: %s\n", a.Data)
}
// VisitB performs an operation on ConcreteElementB.
func (v *ConcreteVisitor) VisitB(b *ConcreteElementB) {
fmt.Printf("Visiting ConcreteElementB with value: %d\n", b.Value)
}
func main() {
elementA := &ConcreteElementA{Data: "Hello"}
elementB := &ConcreteElementB{Value: 42}
visitor := &ConcreteVisitor{}
elementA.Accept(visitor)
elementB.Accept(visitor)
}
The Visitor pattern allows you to add new operations to a hierarchy of objects without modifying the objects themselves. It achieves this by defining a separate “visitor” class that implements the operations, and then having each element in the hierarchy “accept” the visitor, allowing it to perform its operation. This promotes the Open/Closed Principle.
In this C implementation, we have an abstract Animal structure and concrete types like Dog and Cat. The Animal defines an accept method taking a Visitor pointer. Each concrete animal type implements the accept method to call the appropriate visit method on the visitor, passing itself as an argument. The Visitor abstract structure defines visit methods for each concrete animal type. A concrete visitor, AnimalSoundVisitor, demonstrates how to add a new operation (making a sound) without altering the animal classes. This uses function pointers to achieve polymorphism, a common C technique.
#include <stdio.h>
// Forward declaration of Animal
typedef struct Animal Animal;
// Visitor interface
typedef struct Visitor Visitor;
struct Visitor {
void (*visitDog)(Animal* animal);
void (*visitCat)(Animal* animal);
};
// Animal base class
struct Animal {
void (*accept)(Animal* animal, Visitor* visitor);
};
// Concrete animal: Dog
typedef struct Dog Dog;
struct Dog {
Animal base;
};
void dogAccept(Animal* animal, Visitor* visitor) {
visitor->visitDog((Dog*)animal);
}
// Concrete animal: Cat
typedef struct Cat Cat;
struct Cat {
Animal base;
};
void catAccept(Animal* animal, Visitor* visitor) {
visitor->visitCat((Cat*)animal);
}
// Concrete visitor: AnimalSoundVisitor
typedef struct AnimalSoundVisitor AnimalSoundVisitor;
struct AnimalSoundVisitor {
Visitor base;
};
void dogVisit(Animal* animal) {
printf("Woof!\n");
}
void catVisit(Animal* animal) {
printf("Meow!\n");
}
AnimalSoundVisitor createAnimalSoundVisitor() {
AnimalSoundVisitor visitor;
visitor.base.visitDog = dogVisit;
visitor.base.visitCat = catVisit;
return visitor;
}
// Example Usage
int main() {
Dog dog;
dog.base.accept = dogAccept;
Cat cat;
cat.base.accept = catAccept;
AnimalSoundVisitor soundVisitor = createAnimalSoundVisitor();
dog.base.accept(&dog, &soundVisitor.base);
cat.base.accept(&cat, &soundVisitor.base);
return 0;
}
The Visitor pattern allows you to add new operations to a hierarchy of objects without modifying the objects themselves. It achieves this by defining a separate “visitor” class that implements the operations, and then having each element in the hierarchy “accept” the visitor, allowing the visitor to perform its operation on that element. This promotes the Open/Closed Principle.
The C++ example defines a basic Element interface and concrete elements ConcreteElementA and ConcreteElementB. The Visitor interface declares visit methods for each concrete element. ConcreteVisitor implements these visits, defining the operations. The accept method in each element calls the appropriate visit method on the visitor. This implementation leverages polymorphism and double dispatch, common C++ techniques, to achieve the pattern’s goals in a type-safe and efficient manner.
#include <iostream>
#include <vector>
// Element Interface
class Element {
public:
virtual void accept(class Visitor& visitor) = 0;
};
// Concrete Elements
class ConcreteElementA : public Element {
public:
void accept(Visitor& visitor) override {
visitor.visit(*this);
}
void operationA() {
std::cout << "ConcreteElementA: Operation A called." << std::endl;
}
};
class ConcreteElementB : public Element {
public:
void accept(Visitor& visitor) override {
visitor.visit(*this);
}
void operationB() {
std::cout << "ConcreteElementB: Operation B called." << std::endl;
}
};
// Visitor Interface
class Visitor {
public:
virtual void visit(ConcreteElementA& element) = 0;
virtual void visit(ConcreteElementB& element) = 0;
};
// Concrete Visitor
class ConcreteVisitor : public Visitor {
public:
void visit(ConcreteElementA& element) override {
std::cout << "ConcreteVisitor: Visiting ConcreteElementA" << std::endl;
element.operationA();
}
void visit(ConcreteElementB& element) override {
std::cout << "ConcreteVisitor: Visiting ConcreteElementB" << std::endl;
element.operationB();
}
};
int main() {
std::vector<Element*> elements = {new ConcreteElementA(), new ConcreteElementB(), new ConcreteElementA()};
ConcreteVisitor visitor;
for (Element* element : elements) {
element->accept(visitor);
}
for (Element* element : elements) {
delete element;
}
return 0;
}
The Visitor pattern lets you define a new operation without changing the classes of the objects on which it operates. It’s achieved by moving the operation to a separate “visitor” class that accepts different object types as input. This is useful when you have a complex object structure and want to add operations that depend on the object’s type without cluttering the original classes with conditional logic.
This C# example defines a basic expression tree (Add, Subtract, Number) and a Visitor interface with Visit methods for each expression type. Concrete visitors (e.g., PrintVisitor, EvaluateVisitor) implement the interface to perform specific operations. The Accept method in each expression type calls the appropriate Visit method on the visitor, enabling the operation. This approach leverages C#’s interfaces and polymorphism for a clean and extensible solution, fitting the language’s OOP style.
// Visitor Interface
public interface IVisitor
{
void Visit(Add expression);
void Visit(Subtract expression);
void Visit(Number expression);
}
// Expression Base Class
public abstract class Expression
{
public abstract void Accept(IVisitor visitor);
}
// Concrete Expression: Add
public class Add : Expression
{
public Expression Left { get; }
public Expression Right { get; }
public Add(Expression left, Expression right)
{
Left = left;
Right = right;
}
public override void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
// Concrete Expression: Subtract
public class Subtract : Expression
{
public Expression Left { get; }
public Expression Right { get; }
public Subtract(Expression left, Expression right)
{
Left = left;
Right = right;
}
public override void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
// Concrete Expression: Number
public class Number : Expression
{
public int Value { get; }
public Number(int value)
{
Value = value;
}
public override void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
// Concrete Visitor: Print
public class PrintVisitor : IVisitor
{
public void Visit(Add expression)
{
Console.WriteLine("Add");
}
public void Visit(Subtract expression)
{
Console.WriteLine("Subtract");
}
public void Visit(Number expression)
{
Console.WriteLine("Number: " + expression.Value);
}
}
// Concrete Visitor: Evaluate
public class EvaluateVisitor : IVisitor
{
public void Visit(Add expression)
{
Context.Result = expression.Left.Evaluate() + expression.Right.Evaluate();
}
public void Visit(Subtract expression)
{
Context.Result = expression.Left.Evaluate() - expression.Right.Evaluate();
}
public void Visit(Number expression)
{
Context.Result = expression.Value;
}
}
public static class Context
{
public static int Result { get; set; }
}
// Example Usage
public class Example
{
public static void Main(string[] args)
{
Expression expression = new Add(new Number(5), new Subtract(new Number(10), new Number(2)));
PrintVisitor printVisitor = new PrintVisitor();
expression.Accept(printVisitor); // Output: Add, Subtract, Number
EvaluateVisitor evaluateVisitor = new EvaluateVisitor();
expression.Accept(evaluateVisitor);
Console.WriteLine("Result: " + Context.Result); // Output: Result: 3
}
}
The Visitor pattern lets you define a new operation without changing the classes of the objects on which it operates. It’s useful when you have a complex object structure and want to add functionality that depends on the specific types of objects within that structure. Here, we define Element and concrete elements like ConcreteElementA and ConcreteElementB. The Visitor interface declares visit methods for each concrete element. ConcreteVisitor1 and ConcreteVisitor2 implement these visits to perform different operations. The ObjectStructure (a simple array in this case) accepts a visitor and calls the appropriate visit method on each element. This TypeScript implementation leverages interfaces and type safety, common practices in the language, to ensure correct visitor dispatching.
// Element interface
interface Element {
accept(visitor: Visitor): void;
}
// Concrete Elements
class ConcreteElementA implements Element {
operationA(): string {
return "ConcreteElementA operation";
}
accept(visitor: Visitor): void {
visitor.visitConcreteElementA(this);
}
}
class ConcreteElementB implements Element {
operationB(): string {
return "ConcreteElementB operation";
}
accept(visitor: Visitor): void {
visitor.visitConcreteElementB(this);
}
}
// Visitor interface
interface Visitor {
visitConcreteElementA(element: ConcreteElementA): void;
visitConcreteElementB(element: ConcreteElementB): void;
}
// Concrete Visitors
class ConcreteVisitor1 implements Visitor {
visitConcreteElementA(element: ConcreteElementA): void {
console.log("ConcreteVisitor1 visited ConcreteElementA: " + element.operationA());
}
visitConcreteElementB(element: ConcreteElementB): void {
console.log("ConcreteVisitor1 visited ConcreteElementB: " + element.operationB());
}
}
class ConcreteVisitor2 implements Visitor {
visitConcreteElementA(element: ConcreteElementA): void {
console.log("ConcreteVisitor2 visited ConcreteElementA: " + element.operationA().toUpperCase());
}
visitConcreteElementB(element: ConcreteElementB): void {
console.log("ConcreteVisitor2 visited ConcreteElementB: " + element.operationB().repeat(2));
}
}
// Object Structure
class ObjectStructure {
private elements: Element[];
constructor(elements: Element[]) {
this.elements = elements;
}
traverse(visitor: Visitor): void {
this.elements.forEach(element => {
element.accept(visitor);
});
}
}
// Example Usage
const elements = [new ConcreteElementA(), new ConcreteElementB(), new ConcreteElementA()];
const objectStructure = new ObjectStructure(elements);
const visitor1 = new ConcreteVisitor1();
const visitor2 = new ConcreteVisitor2();
objectStructure.traverse(visitor1);
objectStructure.traverse(visitor2);
The Visitor pattern allows you to add new operations to a hierarchy of objects without modifying the objects themselves. It achieves this by defining a separate “visitor” interface that contains visit methods for each concrete element type in the hierarchy. Each concrete visitor implements these methods to define the operation for that element. The elements then “accept” the visitor, delegating the operation to the appropriate visit method.
This JavaScript implementation uses a simple AST (Abstract Syntax Tree) of mathematical expressions. The Expression interface defines the accept method. Concrete expressions like Number and Add implement accept, passing themselves to the visitor. The Visitor interface defines visitNumber and visitAdd. ExpressionPrinter is a concrete visitor that prints the expression. This approach is idiomatic JavaScript as it leverages interfaces (achieved through duck typing) and function dispatch to achieve polymorphism without strict class hierarchies.
// Expression interface
class Expression {
accept(visitor) {
throw new Error("Method 'accept' must be implemented.");
}
}
// Concrete Expressions
class Number extends Expression {
constructor(value) {
super();
this.value = value;
}
accept(visitor) {
visitor.visitNumber(this);
}
}
class Add extends Expression {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
accept(visitor) {
visitor.visitAdd(this);
}
}
// Visitor interface
class Visitor {
visitNumber(number) {
throw new Error("Method 'visitNumber' must be implemented.");
}
visitAdd(add) {
throw new Error("Method 'visitAdd' must be implemented.");
}
}
// Concrete Visitor: Expression Printer
class ExpressionPrinter extends Visitor {
constructor() {
super();
this.result = "";
}
visitNumber(number) {
this.result += number.value;
}
visitAdd(add) {
this.result += "(";
add.left.accept(this);
this.result += "+";
add.right.accept(this);
this.result += ")";
}
getResult() {
return this.result;
}
}
// Example Usage
const expression = new Add(new Number(1), new Add(new Number(2), new Number(3)));
const printer = new ExpressionPrinter();
expression.accept(printer);
console.log(printer.getResult()); // Output: (1+(2+3))
The Visitor pattern allows you to add new operations to a hierarchy of objects without modifying the objects themselves. It achieves this by defining a separate “visitor” class that contains the new operation, and then providing a method in each element of the hierarchy to “accept” the visitor. The visitor then implements the specific operation for each element type. This promotes the Open/Closed Principle.
The Python example defines an Element interface with an accept method. Concrete elements like ConcreteElementA and ConcreteElementB implement this interface. The Visitor interface defines visit_a and visit_b methods. ConcreteVisitor implements these to perform specific operations on each element. The client code creates elements and a visitor, then calls accept on each element, passing the visitor. This triggers the appropriate visit method in the visitor, executing the operation. This approach is Pythonic due to its reliance on duck typing and interfaces (implicitly defined through method signatures).
# Element interface
class Element:
def accept(self, visitor):
raise NotImplementedError
# Concrete Elements
class ConcreteElementA(Element):
def accept(self, visitor):
visitor.visit_a(self)
class ConcreteElementB(Element):
def accept(self, visitor):
visitor.visit_b(self)
# Visitor interface
class Visitor:
def visit_a(self, element):
raise NotImplementedError
def visit_b(self, element):
raise NotImplementedError
# Concrete Visitor
class ConcreteVisitor(Visitor):
def visit_a(self, element):
print(f"ConcreteVisitor visited ConcreteElementA: {element}")
def visit_b(self, element):
print(f"ConcreteVisitor visited ConcreteElementB: {element}")
# Client code
if __name__ == "__main__":
elements = [ConcreteElementA(), ConcreteElementB()]
visitor = ConcreteVisitor()
for element in elements:
element.accept(visitor)
The Visitor pattern is a behavioral design pattern that lets you define a new operation without changing the classes of the objects on which it operates. It’s achieved by “moving” the operational logic into separate visitor classes. Each visitor class implements a visit method for each concrete element type in the object structure.
The code demonstrates a simple expression tree with Expression as the base class and concrete implementations like Literal, Add, and Multiply. The Visitor interface defines visit methods for each expression type. ExpressionEvaluator is a concrete visitor that evaluates the expression tree. This approach is idiomatic Java because it leverages interfaces and polymorphism to achieve flexibility and maintainability. It avoids modifying the core expression classes when adding new evaluation logic (or other operations).
// Define the Element interface
interface Expression {
void accept(Visitor visitor);
}
// Concrete Elements
class Literal implements Expression {
private int value;
public Literal(int value) {
this.value = value;
}
public int getValue() {
return value;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Add implements Expression {
private Expression left;
private Expression right;
public Add(Expression left, Expression right) {
this.left = left;
this.right = right;
}
public Expression getLeft() {
return left;
}
public Expression getRight() {
return right;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Multiply implements Expression {
private Expression left;
private Expression right;
public Multiply(Expression left, Expression right) {
this.left = left;
this.right = right;
}
public Expression getLeft() {
return left;
}
public Expression getRight() {
return right;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// Define the Visitor interface
interface Visitor {
int visit(Literal literal);
int visit(Add add);
int visit(Multiply multiply);
}
// Concrete Visitor
class ExpressionEvaluator implements Visitor {
@Override
public int visit(Literal literal) {
return literal.getValue();
}
@Override
public int visit(Add add) {
return visit(add.getLeft()) + visit(add.getRight());
}
@Override
public int visit(Multiply multiply) {
return visit(multiply.getLeft()) * visit(multiply.getRight());
}
}
// Example Usage
public class VisitorExample {
public static void main(String[] args) {
Expression expression = new Add(new Multiply(new Literal(2), new Literal(3)), new Literal(5));
Visitor evaluator = new ExpressionEvaluator();
expression.accept(evaluator);
System.out.println("Result: " + evaluator.visit(expression)); // This line is redundant, but shows the pattern
}
}