Wynn5a 的技术博客
技术分享约 2 分钟读完

如何在 Rust 中实现 Visitor 模式

什么是 Visitor 模式#

我的行文特点就是先从基础的概念说起,然后慢慢通过概念本身顶层设计的讲解,让大家在整体层面上对这个概念有一定的了解,进而再通过代码实现它,这样读者就更有一种融会贯通的感觉,对知识的掌握也就更加牢固。

Visitor 模式翻译为中文即 “访问者模式”,其基本的设计思路为“给结构稳定的对象(对象族)提供一个可供外部访问的扩展点”,这样如果我想对这个对象进行其本身之外的操作,只需要藉由此扩展点访问其内部稳固的属性即可,无需更改对象本身即可达成扩展的目的,实现了将算法与其所作用的对象隔离开的目的。

典型的 Visitor 模式实现原型如下图所示

原图 - https://refactoringguru.cn/images/patterns/diagrams/visitor/structure-zh-2x.png
原图 - https://refactoringguru.cn/images/patterns/diagrams/visitor/structure-zh-2x.png

上图中的 Element::accept(v:Visitor) 即前文所述的扩展点,将对自己的访问/操作权限转移给 Visitor, 这样后续的扩展可以通过实现不同的 Visitor 完成。

设计模式尤其是 GoF 的 23 种设计模式大家都比较熟悉了,各种资料也是随处可见,本文就不在详细展开图中各个组件及其作用,但是需要重点强调下 Visitor 接口的实现应根据实际情况来决定。图中使用了“重载 Overloading”的方案来完成对不同对象的访问,由编译器来静态绑定可以访问的对象,在不支持重载技术的语言中,我们需要在 Visitor 中定义不同的方法,然后由具体的 Element 来选择自己需要的方法。

如何在 Rust 中实现 Visitor 模式#

Rust 是一种系统编程语言,旨在提供 C++ 级别的性能的同时提供更好的内存安全性和并发特性。我们可以利用 Rust 的零成本抽象能力来实现此设计模式,比如 Rust 的特征(trait)系统提供了强大的多态性,而且大多数特征方法的调用在编译时就能解析绑定,避免了虚函数调用的开销,本文中的实例就使用了此特性。

  1. 定义需要扩展的对象(族),开放扩展点给 Visitor trait

    rust
    // Define the Shape trait
    trait Shape {
        fn accept(&self, visitor: &dyn Visitor);
    }
     
    // Implement Circle using Shape trit
    struct Circle {
        radius: f64,
    }
     
    impl Circle {
        fn new(radius: f64) -> Self {
            Circle { radius }
        }
    }
     
    impl Shape for Circle {
        fn accept(&self, visitor: &dyn Visitor) {
            visitor.visit_circle(self); // handover operations to visitor
        }
    }
     
    // Implement Rectangle using Shape trait
    struct Rectangle {
        width: f64,
        height: f64,
    }
     
    impl Rectangle {
        fn new(width: f64, height: f64) -> Self {
            Rectangle { width, height }
        }
    }
     
    impl Shape for Rectangle {
        fn accept(&self, visitor: &dyn Visitor) {
            visitor.visit_rectangle(self); // handover operations to visitor
        }
    }
  2. 定义 Visitor

    rust
    // Define the Visitor trait
    trait Visitor {
        fn visit_circle(&self, circle: &Circle);
        fn visit_rectangle(&self, rectangle: &Rectangle);
    }
     
     
    // Implement a concrete visitor: AreaCalculator
    struct AreaCalculator;
     
    impl Visitor for AreaCalculator {
        fn visit_circle(&self, circle: &Circle) {
            let area = std::f64::consts::PI * circle.radius * circle.radius;
            println!("Circle area: {:.2}", area);
        }
     
        fn visit_rectangle(&self, rectangle: &Rectangle) {
            let area = rectangle.width * rectangle.height;
            println!("Rectangle area: {:.2}", area);
        }
    }
     
    // Implement a concrete visitor: PerimeterCalculator
    struct PerimeterCalculator;
     
    impl Visitor for PerimeterCalculator {
        fn visit_circle(&self, circle: &Circle) {
            let perimeter = 2.0 * std::f64::consts::PI * circle.radius;
            println!("Circle perimeter: {:.2}", perimeter);
        }
     
        fn visit_rectangle(&self, rectangle: &Rectangle) {
            let perimeter = 2.0 * (rectangle.width + rectangle.height);
            println!("Rectangle perimeter: {:.2}", perimeter);
        }
    }
    // And more Visitors as you like
  3. 使用 Visitor 来实现对不同对象的扩展

    rust
    fn main() {
        let shapes: Vec<Box<dyn Shape>> = vec![
            Box::new(Circle::new(5.0)),
            Box::new(Rectangle::new(4.0, 6.0)),
        ];
     
        let area_calculator = AreaCalculator;
        let perimeter_calculator = PerimeterCalculator;
     
        for shape in &shapes {
            shape.accept(&area_calculator);
            shape.accept(&perimeter_calculator);
            println!();
        }
    }

总结#

本文虽短且简单,也不失为一个展示 Rust 语言设计能力的例子,希望对大家有所帮助。

读完了,谢谢你读到这里

有新文章时想第一时间看到?订阅 RSS