Visitor允许您将“外部”操作添加到整个类层次结构中,而无需更改这些类的现有代码。
访问者另外一个定义是:封装了一种对异构对象集合进行操作的算法。它允许在同一数据上写入多个不同的算法,而无需修改数据(或其主要行为)。
访问者模式在您想将算法应用于异构数据的任何地方都很有用。如果数据是同质的 同类型的,您可以使用类似迭代器的模式。使用访问者对象(而不是函数方法)允许访问者是有状态的,从而在节点之间传递信息。
访问者模式的目标不仅仅是“允许在异构的数据上编写多个不同的算法” . 使用访问者模式的主要原因是允许动态双重调度。假设您有一对抽象的访问者和数据,并且您在编译时不知道数据和访问者的实际类型是什么,您可以说 data.accept(visitor) 并让魔法发生。这在任何 OO 语言中都是完全可行的
在Rust实现例如:
let d : Data = some of(Stmt, Name, Expr, ...); let v : Visitor = some of(Interpreter, ...);
|
并通过 d.accept(v) 而不对任何地方的类型进行匹配。trait Data { fn accept<V: Visitor>(&self, visitor: &mut V) -> V::Result; }
trait Visitor { type Result;
fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Result; fn visit_name(&mut self, name: &Name) -> Self::Result; fn visit_expr(&mut self, expr: &Expr) -> Self::Result; }
impl Data for Stmt { fn accept<V: Visitor>(&self, visitor: &mut V) -> V::Result { visitor.visit_stmt(self) } }
impl Data for Name { fn accept<V: Visitor>(&self, visitor: &mut V) -> V::Result { visitor.visit_name(self) } }
impl Data for Expr { fn accept<V: Visitor>(&self, visitor: &mut V) -> V::Result { visitor.visit_expr(self) } }
impl Visitor for Interpreter { fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Result { unimplemented!() } fn visit_name(&mut self, name: &Name) -> Self::Result { unimplemented!() } fn visit_expr(&mut self, expr: &Expr) -> Self::Result { unimplemented!() } }
|
Serde以这种方式使用访问者将序列化/反序列化与数据格式分离。
serde 使用访问者模式对其中间表示进行编码。如果它使用模式匹配而不是访问者模式,则必须将其中间表示实例化为枚举,这会增加不必要的开销。
specialization
上述代码可通过specialization 实现如下:
#![feature(specialization)]
pub trait Visitor<T> { fn visit(&mut self, t:&T); }
pub trait Visitable: Sized { fn accept<T>(&self, t: &mut T) where T: Visitor<Self> { t.visit(self); } }
struct Expr; impl Visitable for Expr {}
struct Term; impl Visitable for Term {}
struct Vis;
impl <T> Visitor<T> for Vis where T: Visitable{ default fn visit(&mut self, _: &T) { unimplemented!(); } }
impl Visitor<Expr> for Vis { fn visit(&mut self, _: &Expr) { println!("Visiting an Expression"); } }
impl Visitor<Term> for Vis { fn visit(&mut self, _: &Term) { println!("Visiting a Term"); } }
fn main() { let mut v = Vis; Expr.accept(&mut v); Term.accept(&mut v); }
|
函数式编程中”折叠fold“类似访问者模式,对数据集合中的每个项目运行算法以创建一个新项目,从而创建一个全新的集合。