是否存在设计模式可约束Device.read()后立即调用Analyser.next()?
如何设计API强制读取后立即调用分析方法?
你的核心问题是要确保Device.read()和analyser.next()之间没有延迟,因为分析逻辑依赖连续读取的时间差。以下是几种可行的设计方案,结合Rust特性实现约束:
方案1:封装读取与分析的原子操作
直接在Analyser中提供方法,让用户传入读取逻辑,由Analyser内部完成「读取+分析」的原子操作,从根本上避免中间插入延迟的可能。
示例代码:
use std::time::Instant; #[derive(Default)] struct AnalyserOutput; struct Analyser { last_read_time: Option<Instant>, } impl Analyser { fn new() -> Self { Self { last_read_time: None } } // 接收读取闭包,内部执行读取+分析的原子操作 fn read_and_analyse<F>(&mut self, read_fn: F) -> (f64, AnalyserOutput) where F: FnOnce() -> f64, { let read_val = read_fn(); let now = Instant::now(); let output = self.calculate_feedback(read_val, now); self.last_read_time = Some(now); (read_val, output) } // 内部分析逻辑,基于时间差计算反馈 fn calculate_feedback(&mut self, read_val: f64, current_time: Instant) -> AnalyserOutput { // 省略具体时间差计算逻辑 AnalyserOutput } } // 用户侧调用示例 fn main() { let device = TemperatureGauge::Device::new(); let mut analyser = Analyser::new(); loop { let (read_val, output) = analyser.read_and_analyse(|| device.read()); device.set(read_val + output); std::thread::sleep(std::time::Duration::from_millis(1)); } } // 模拟用户自定义设备 mod TemperatureGauge { pub struct Device; impl Device { pub fn new() -> Self { Self } pub fn read(&self) -> f64 { 25.0 } pub fn set(&self, _val: f64) {} } }
优点:完全杜绝用户在读取和分析之间插入操作,逻辑简单直接。
缺点:用户必须通过你的方法执行读取,无法直接调用device.read()。
方案2:利用类型状态机强制调用顺序
借助Rust的类型系统,通过状态枚举约束Analyser的调用流程:只有在「已读取未分析」状态下才能调用分析方法,分析完成后才能回到「可读取」状态,从编译层面强制顺序。
示例代码:
use std::time::Instant; #[derive(Default)] struct AnalyserOutput; // 定义Analyser的状态 enum AnalyserState { ReadyToRead, NeedsAnalysis(f64, Instant), } struct Analyser<S> { state: S, // 其他分析状态字段 } impl Analyser<AnalyserState::ReadyToRead> { fn new() -> Self { Self { state: AnalyserState::ReadyToRead } } // 仅ReadyToRead状态可调用读取,返回NeedsAnalysis状态的Analyser fn read<F>(self, read_fn: F) -> Analyser<AnalyserState::NeedsAnalysis> where F: FnOnce() -> f64, { let read_val = read_fn(); let now = Instant::now(); Analyser { state: AnalyserState::NeedsAnalysis(read_val, now), } } } impl Analyser<AnalyserState::NeedsAnalysis> { // 仅NeedsAnalysis状态可调用分析,返回ReadyToRead状态的Analyser和反馈 fn analyse(mut self) -> (Analyser<AnalyserState::ReadyToRead>, AnalyserOutput) { let (read_val, current_time) = match self.state { AnalyserState::NeedsAnalysis(val, time) => (val, time), _ => unreachable!(), }; let output = self.calculate_feedback(read_val, current_time); (Analyser { state: AnalyserState::ReadyToRead }, output) } fn calculate_feedback(&mut self, read_val: f64, current_time: Instant) -> AnalyserOutput { // 省略具体时间差计算逻辑 AnalyserOutput } } // 用户侧调用示例 fn main() { let device = TemperatureGauge::Device::new(); let mut analyser = Analyser::new(); loop { // 读取后进入NeedsAnalysis状态 let analyser_after_read = analyser.read(|| device.read()); // 必须立即分析才能回到ReadyToRead状态 let (new_analyser, output) = analyser_after_read.analyse(); analyser = new_analyser; device.set(output); std::thread::sleep(std::time::Duration::from_millis(1)); } } // 模拟用户自定义设备 mod TemperatureGauge { pub struct Device; impl Device { pub fn new() -> Self { Self } pub fn read(&self) -> f64 { 25.0 } pub fn set(&self, _val: AnalyserOutput) {} } }
优点:编译期就能检查调用顺序错误,无需运行时panic(可替换为Result实现更优雅的错误处理),完全强制约束。
缺点:API设计稍复杂,用户需要理解状态流转逻辑。
方案3:通过Trait要求带时间戳的读取(弱约束)
定义一个trait要求设备返回带时间戳的读取结果,同时在文档中明确要求用户必须在读取完成后立即传入Analyser。
示例代码:
use std::time::Instant; #[derive(Default)] struct AnalyserOutput; trait Measurable { fn read_with_timestamp(&self) -> (f64, Instant) { let now = Instant::now(); (self.read(), now) } fn read(&self) -> f64; } struct Analyser { last_read_time: Option<Instant>, } impl Analyser { fn new() -> Self { Self { last_read_time: None } } fn next(&mut self, read_val: f64, timestamp: Instant) -> AnalyserOutput { // 使用传入的时间戳计算反馈 self.last_read_time = Some(timestamp); AnalyserOutput } } // 用户设备实现Measurable trait mod TemperatureGauge { use super::{Measurable, Instant}; pub struct Device; impl Measurable for Device { fn read(&self) -> f64 { 25.0 } } impl Device { pub fn new() -> Self { Self } pub fn set(&self, _val: f64) {} } } // 用户侧调用示例 fn main() { let device = TemperatureGauge::Device::new(); let mut analyser = Analyser::new(); loop { let (read_val, timestamp) = device.read_with_timestamp(); // 文档强制要求:此处必须立即调用next,不得插入任何延迟操作 let output = analyser.next(read_val, timestamp); device.set(read_val + output); std::thread::sleep(std::time::Duration::from_millis(1)); } }
优点:侵入性低,用户只需实现简单的trait。
缺点:依赖用户遵守文档约束,无法从代码层面强制,只能靠明确的文档说明。
总结
如果需要强约束,优先选择方案1或方案2;如果希望API更灵活,可选择方案3并配合清晰的文档说明。
内容的提问来源于stack exchange,提问作者Kiwi breeder
相关产品推荐
相关产品推荐

