如何在 Rust 中实现 Visitor 模式
技术分享
Aug 31, 2024
type
Post
status
Published
date
Aug 31, 2024
slug
visitor-pattern-in-rust
summary
最近在学习《Crafting Interpreters》一书,并尝试使用 Rust 实现一个 Interpreter,其中需要使用到设计模式 - Visitor 模式 - 来实现 Parser,故利用本文给大家介绍一下如何使用 Rust 语言正确实现 Visitor 模式,也作学习记录一用
tags
Design Pattern
Rust
category
技术分享
icon
password
Property
Sep 3, 2024 02:34 AM

什么是 Visitor 模式

我的行文特点就是先从基础的概念说起,然后慢慢通过概念本身顶层设计的讲解,让大家在整体层面上对这个概念有一定的了解,进而再通过代码实现它,这样读者就更有一种融会贯通的感觉,对知识的掌握也就更加牢固。
Visitor 模式翻译为中文即 “访问者模式”,其基本的设计思路为“给结构稳定的对象(对象族)提供一个可供外部访问的扩展点”,这样如果我想对这个对象进行其本身之外的操作,只需要藉由此扩展点访问其内部稳固的属性即可,无需更改对象本身即可达成扩展的目的,实现了将算法与其所作用的对象隔离开的目的。
典型的 Visitor 模式实现原型如下图所示
上图中的 Element::accept(v:Visitor) 即前文所述的扩展点,将对自己的访问/操作权限转移给 Visitor, 这样后续的扩展可以通过实现不同的 Visitor 完成。
设计模式尤其是 GoF 的 23 种设计模式大家都比较熟悉了,各种资料也是随处可见,本文就不在详细展开图中各个组件及其作用,但是需要重点强调下 Visitor 接口的实现应根据实际情况来决定。图中使用了“重载 Overloading”的方案来完成对不同对象的访问,由编译器来静态绑定可以访问的对象,在不支持重载技术的语言中,我们需要在 Visitor 中定义不同的方法,然后由具体的 Element 来选择自己需要的方法。

如何在 Rust 中实现 Visitor 模式

Rust 是一种系统编程语言,旨在提供 C++ 级别的性能的同时提供更好的内存安全性和并发特性。我们可以利用 Rust 的零成本抽象能力来实现此设计模式,比如 Rust 的特征(trait)系统提供了强大的多态性,而且大多数特征方法的调用在编译时就能解析绑定,避免了虚函数调用的开销,本文中的实例就使用了此特性。
  1. 定义需要扩展的对象(族),开放扩展点给 Visitor trait
    1. // 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 } }
  1. 定义 Visitor
    1. // 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
  1. 使用 Visitor 来实现对不同对象的扩展
    1. 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 语言设计能力的例子,希望对大家有所帮助。
  • Design Pattern
  • Rust
  • 递归“扫盲”贴
    详解 ClickHouse 中的 MergeTree 引擎工作原理-2