假设我们想要在运行时切换对象实现,我们会做这样的事情:
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 结构以某种方式“注册”它们自己,以便以后可以通过工厂方法查找它们?我很乐意最终得到一个神奇的宏,甚至是一个插件来完成它。
例如,在 D 中,您可以使用模板来注册实现——如果您出于某种原因不能,您始终可以在编译时检查模块并通过混入生成新代码;还有一些用户定义的属性可以帮助解决这个问题。在 Python 中,您通常会使用元类,这样每次创建新的子类时,它的引用都会存储在元类的注册表中,这样您就可以按名称或参数查找实现;如果实现是简单的函数,这也可以通过装饰器来完成。
理想情况下,在上面的示例中,您可以将对象创建为
Object::new(0)
其中值 0
仅在运行时 已知,它会神奇地返回一个对象 { methods: &Methods_0 },而 new() 的主体将 不要像“方法:[&Methods; 2] = [&Methods_0, &Methods_1]”那样硬编码实现,而应该以某种方式自动推断。
最佳答案
所以,这可能是非常错误的,但它可以作为概念证明。
可以使用 Cargo 的 code generation support在编译时进行自省(introspection),通过解析(在这种情况下不完全解析,但你明白了)当前的实现,并生成使 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),
}
}
}
关于metaprogramming - "Registering"特征实现+特征对象的工厂方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28351778/