CODESAMPLE

Visitor - Scala

Share on:

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
  }
}