Rust语言之GoF设计模式:Visitor访问者游客模式


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“类似访问者模式,对数据集合中的每个项目运行算法以创建一个新项目,从而创建一个全新的集合。