You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
patterns/src/patterns/behavioural/visitor.md

116 lines
3.1 KiB
Markdown

9 years ago
# Visitor
## Description
A visitor encapsulates an algorithm that operates over a heterogeneous
collection of objects. It allows multiple different algorithms to be written
over the same data without having to modify the data (or their primary
behaviour).
Furthermore, the visitor pattern allows separating the traversal of a collection
of objects from the operations performed on each object.
9 years ago
## Example
```rust,ignore
9 years ago
// The data we will visit
mod ast {
pub enum Stmt {
Expr(Expr),
Let(Name, Expr),
}
pub struct Name {
value: String,
}
pub enum Expr {
IntLit(i64),
Add(Box<Expr>, Box<Expr>),
Sub(Box<Expr>, Box<Expr>),
}
}
// The abstract visitor
mod visit {
use ast::*;
pub trait Visitor<T> {
fn visit_name(&mut self, n: &Name) -> T;
fn visit_stmt(&mut self, s: &Stmt) -> T;
fn visit_expr(&mut self, e: &Expr) -> T;
}
}
use ast::*;
use visit::*;
9 years ago
// An example concrete implementation - walks the AST interpreting it as code.
struct Interpreter;
impl Visitor<i64> for Interpreter {
fn visit_name(&mut self, n: &Name) -> i64 {
panic!()
}
9 years ago
fn visit_stmt(&mut self, s: &Stmt) -> i64 {
match *s {
Stmt::Expr(ref e) => self.visit_expr(e),
Stmt::Let(..) => unimplemented!(),
}
}
fn visit_expr(&mut self, e: &Expr) -> i64 {
match *e {
Expr::IntLit(n) => n,
Expr::Add(ref lhs, ref rhs) => self.visit_expr(lhs) + self.visit_expr(rhs),
Expr::Sub(ref lhs, ref rhs) => self.visit_expr(lhs) - self.visit_expr(rhs),
}
}
}
```
One could implement further visitors, for example a type checker, without having
to modify the AST data.
## Motivation
The visitor pattern is useful anywhere that you want to apply an algorithm to
heterogeneous data. If data is homogeneous, you can use an iterator-like
pattern. Using a visitor object (rather than a functional approach) allows the
visitor to be stateful and thus communicate information between nodes.
9 years ago
## Discussion
It is common for the `visit_*` methods to return void (as opposed to in the
example). In that case it is possible to factor out the traversal code and share
it between algorithms (and also to provide noop default methods). In Rust, the
common way to do this is to provide `walk_*` functions for each datum. For
example,
```rust,ignore
9 years ago
pub fn walk_expr(visitor: &mut Visitor, e: &Expr) {
match *e {
Expr::IntLit(_) => {}
9 years ago
Expr::Add(ref lhs, ref rhs) => {
visitor.visit_expr(lhs);
visitor.visit_expr(rhs);
}
Expr::Sub(ref lhs, ref rhs) => {
visitor.visit_expr(lhs);
visitor.visit_expr(rhs);
}
}
}
```
In other languages (e.g., Java) it is common for data to have an `accept` method
which performs the same duty.
## See also
The visitor pattern is a common pattern in most OO languages.
[Wikipedia article](https://en.wikipedia.org/wiki/Visitor_pattern)
The [fold](../creational/fold.md) pattern is similar to visitor but produces a
new version of the visited data structure.