2015-02-05 67 views
2

说,我们希望有对象实现在运行时切换,我们会做这样的事:现在“注册”特征实现+工厂方法对象

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中,您可以使用模板来注册实现 - 如果由于某种原因无法完成,则可以在编译时检查模块,并通过mixin生成新代码;还有一些用户定义的属性可以帮助解决这个问题。在Python中,您通常会使用元类,以便每次创建新的子类时,都会将其引用存储在元类的注册表中,以便您按名称或参数查找实现;如果实现是简单的函数,这也可以通过装饰器完成。

理想的情况下,在上面的例子中,你将能够创建对象为

Object::new(0) 

其中值0运行只知道,它会神奇地回到你的对象{方法:& Methods_0} ,并且new()的主体而不是的实现具有类似于“方法:[&Methods; 2] = [& Methods_0,& Methods_1]”的硬编码“,而应该以某种方式自动推断。

+0

作为一个小挑剔,你将需要返回一个'箱'而不是'&Method'。'&Method'没有安全空间指向(除非你有一个''static''实例)。 – Shepmaster 2015-02-05 19:25:35

+0

如果你不需要在'main'方法中维护数组,你的当前解决方案是可以接受的吗?你有什么方向*如何*选择具体的实施?好像你有一个Orc方法的地图=> OrcMethods',但是在那个时候,你可以直接调用OrcMethods。 – Shepmaster 2015-02-05 19:30:35

+0

谢谢@Shepmaster,你肯定对盒子是对的。 – aldanor 2015-02-05 20:02:43

回答

1

所以,这可能是非常错误的,但它作为一个概念证明。

它可以使用货运的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), 
     } 
    } 
}