假设我们希望在运行时切换对象实现,我们可以这样做:
pub trait Methods {
fn func(&self);
}
pub struct Methods_0;
impl Methods for Methods_0 {
fn func(&self) {
println!("foo");
}
}
pub struct Methods_1;
impl Methods for Methods_1 {
fn func(&self) {
println!("bar");
}
}
pub struct Object<'a> { //'
methods: &'a (Methods + 'a),
}
fn main() {
let methods: [&Methods; 2] = [&Methods_0, &Methods_1];
let mut obj = Object { methods: methods[0] };
obj.methods.func();
obj.methods = methods[1];
obj.methods.func();
}
现在,如果有数百个这样的实现怎么办?例如。想象一下用于收藏卡片游戏的卡片的实现,其中每张卡片做一些完全不同的东西并且难以概括;或者想象一下巨型状态机的操作码的实现。当然你可以争辩说可以使用不同的设计模式 - 但这不是这个问题的重点...
想知道这些Impl结构是否有任何方式以某种方式&#34;注册&#34;他们自己以后可以通过工厂方法查找它们?我很乐意最终得到一个神奇的宏甚至插件来实现它。
说,在D中你可以使用模板来注册实现 - 如果由于某种原因你不能这样做,你总是可以在编译时检查模块并通过mixin生成新的代码;还有一些用户定义的属性可以帮助解决这个问题。在Python中,您通常会使用元类,以便每次创建新的子类时,对它的引用都存储在元类的注册表中,该注册表允许您按名称或参数查找实现;如果实现是简单的函数,这也可以通过装饰器来完成。
理想情况下,在上面的示例中,您可以将对象创建为
Object::new(0)
其中值0
仅在运行时中已知,它会神奇地返回一个Object {methods:&amp; Methods_0},而new()的主体将不的实现硬编码如此&#34;方法:[&amp;方法; 2] = [&amp; Methods_0,&amp; Methods_1]&#34;相反,它应该以某种方式自动推断。
答案 0 :(得分:1)
所以,这可能是非常错误的,但它可以作为概念证明。
可以使用Cargo的code generation support在编译时进行内省,通过解析(在这种情况下不完全解析,但你明白了)当前的实现,并生成使Object::new()
工作所需的样板。
代码非常复杂,没有任何错误处理,但有效。
在rustc 1.0.0-dev (2c0535421 2015-02-05 15:22:48 +0000)
上进行测试
(See on github)
src/main.rs
:
pub mod implementations;
mod generated_glue {
include!(concat!(env!("OUT_DIR"), "/generated_glue.rs"));
}
use generated_glue::Object;
pub trait Methods {
fn func(&self);
}
pub struct Methods_2;
impl Methods for Methods_2 {
fn func(&self) {
println!("baz");
}
}
fn main() {
Object::new(2).func();
}
src/implementations.rs
:
use super::Methods;
pub struct Methods_0;
impl Methods for Methods_0 {
fn func(&self) {
println!("foo");
}
}
pub struct Methods_1;
impl Methods for Methods_1 {
fn func(&self) {
println!("bar");
}
}
build.rs
:
#![feature(core, unicode, path, io, env)]
use std::env;
use std::old_io::{fs, File, BufferedReader};
use std::collections::HashMap;
fn main() {
let target_dir = Path::new(env::var_string("OUT_DIR").unwrap());
let mut target_file = File::create(&target_dir.join("generated_glue.rs")).unwrap();
let source_code_path = Path::new(file!()).join_many(&["..", "src/"]);
let source_files = fs::readdir(&source_code_path).unwrap().into_iter()
.filter(|path| {
match path.str_components().last() {
Some(Some(filename)) => filename.split('.').last() == Some("rs"),
_ => false
}
});
let mut implementations = HashMap::new();
for source_file_path in source_files {
let relative_path = source_file_path.path_relative_from(&source_code_path).unwrap();
let source_file_name = relative_path.as_str().unwrap();
implementations.insert(source_file_name.to_string(), vec![]);
let mut file_implementations = &mut implementations[*source_file_name];
let mut source_file = BufferedReader::new(File::open(&source_file_path).unwrap());
for line in source_file.lines() {
let line_str = match line {
Ok(line_str) => line_str,
Err(_) => break,
};
if line_str.starts_with("impl Methods for Methods_") {
const PREFIX_LEN: usize = 25;
let number_len = line_str[PREFIX_LEN..].chars().take_while(|chr| {
chr.is_digit(10)
}).count();
let number: i32 = line_str[PREFIX_LEN..(PREFIX_LEN + number_len)].parse().unwrap();
file_implementations.push(number);
}
}
}
writeln!(&mut target_file, "use super::Methods;").unwrap();
for (source_file_name, impls) in &implementations {
let module_name = match source_file_name.split('.').next() {
Some("main") => "super",
Some(name) => name,
None => panic!(),
};
for impl_number in impls {
writeln!(&mut target_file, "use {}::Methods_{};", module_name, impl_number).unwrap();
}
}
let all_impls = implementations.values().flat_map(|impls| impls.iter());
writeln!(&mut target_file, "
pub struct Object;
impl Object {{
pub fn new(impl_number: i32) -> Box<Methods + 'static> {{
match impl_number {{
").unwrap();
for impl_number in all_impls {
writeln!(&mut target_file,
" {} => Box::new(Methods_{}),", impl_number, impl_number).unwrap();
}
writeln!(&mut target_file, "
_ => panic!(\"Unknown impl number: {{}}\", impl_number),
}}
}}
}}").unwrap();
}
生成的代码:
use super::Methods;
use super::Methods_2;
use implementations::Methods_0;
use implementations::Methods_1;
pub struct Object;
impl Object {
pub fn new(impl_number: i32) -> Box<Methods + 'static> {
match impl_number {
2 => Box::new(Methods_2),
0 => Box::new(Methods_0),
1 => Box::new(Methods_1),
_ => panic!("Unknown impl number: {}", impl_number),
}
}
}