问题陈述
我有一组 struct ,A
、B
、C
和D
,它们都实现一个特征
Runnable
.
trait Runnable {
fn run(&mut self);
}
impl Runnable for A {...}
impl Runnable for B {...}
impl Runnable for C {...}
impl Runnable for D {...}
我还有一个 struct Config
,它用作构造A
的规范,
B
、C
和D
个实例.
struct Config {
filename: String,
other_stuff: u8,
}
impl From<Config> for A {...}
impl From<Config> for B {...}
impl From<Config> for C {...}
impl From<Config> for D {...}
在我的程序中,我想解析一个Config
实例并构造一个A
,
B
、C
或D
,取决于filename
字段的值,然后调用
Runnable::run
在上面.应该通过按顺序判断每个 struct 与filename
字符串,并 Select 与该字符串"匹配"的第一个 struct 来 Select struct .
天真的实施
以下是一个天真的实现.
trait CheckFilename {
fn check_filename(filename: &str) -> bool;
}
impl CheckFilename for A {...}
impl CheckFilename for B {...}
impl CheckFilename for C {...}
impl CheckFilename for D {...}
fn main() {
let cfg: Config = get_config(); // Some abstract way of evaluating a Config at runtime.
let mut job: Box<dyn Runnable> = if A::check_filename(&cfg.filename) {
println!("Found matching filename for A");
Box::new(A::from(cfg))
} else if B::check_filename(&cfg.filename) {
println!("Found matching filename for B");
Box::new(B::from(cfg))
} else if C::check_filename(&cfg.filename) {
println!("Found matching filename for C");
Box::new(C::from(cfg))
} else if D::check_filename(&cfg.filename) {
println!("Found matching filename for D");
Box::new(D::from(cfg))
} else {
panic!("did not find matching pattern for filename {}", cfg.filename);
};
job.run();
}
这是可行的,但有一些代码气味:
- 巨人
if else if else if else if else...
声明臭气熏天 - 大量重复:用于判断文件名、打印 struct 类型的代码 和从配置构造实例对于每个分支都是相同的 唯一不同的是它们处理的是哪种 struct 类型.有没有办法把这个抽象出来? 不停地重复?
- 非常容易出错:很容易意外地搞错文件名之间的映射
字符串和 struct ,因为未能将 struct 与谓词同步;for
编写类似以下内容的示例:
而编译器不会捕捉到这一点.if D::check_filename(&cfg.filename) { println!("Found matching filename for D"); Box::new(B::from(cfg)) // Developer error: constructs a B instead of a D. }
- 向程序添加新的 struct (例如,
E
、F
、G
等)不是很符合人体工程学.它需要在主if else
语句中 for each 语句添加一个新分支.简单地将 struct 添加到某种 struct 类型的"主列表"中会好得多.
有没有更优雅或更惯用的方式来解决这些气味?