Rust中的命令模式

时间:2016-06-12 21:07:32

标签: rust

我试图在Rust中实现命令行服务器应用程序。 我希望有一个命令列表(或哈希表),我可以迭代这些命令来打印用法和查找/执行命令。

我的问题是每个Command都需要对命令用来执行的事物的可变引用(例如将用户插入数据库或其他东西)。借用检查员显然不喜欢传递多个可变引用。有没有办法做到这一点?我首先让execute方法引用它所需要的东西,然后我需要一个单独的列表来查找需要不同的东西的命令,听起来它会变得笨拙。

以下是问题的一个示例:

struct SomeStruct {
    pub some_field: String,
}

impl SomeStruct {
    pub fn new(field: String) -> SomeStruct {
        let some_struct = SomeStruct {
            some_field: field,
        };
        return some_struct;
    }

    pub fn change_field(&mut self) {
        self.some_field = "Something else".to_string();
    }
}

struct SomeCommand<'a> {
    pub some_struct: &'a mut SomeStruct,
}

impl<'a> SomeCommand<'a> {
    pub fn new(the_struct: &'a mut SomeStruct) -> SomeCommand {
        let some_command = SomeCommand {
            some_struct: the_struct,
        };
        return some_command;
    }

    pub fn execute(&mut self) {
        self.some_struct.change_field();
    }
}

fn main() {
    let mut some_struct = SomeStruct::new("hey".to_string());
    let some_command1 = SomeCommand::new(&mut some_struct);

    // Compiler complains because I'm making another mutable binding to some_struct
    let some_command2 = SomeCommand::new(&mut some_struct);
}

有更好的方法吗?

2 个答案:

答案 0 :(得分:2)

我认为将可变引用作为execute()的参数传递而不是将其存储在SomeCommand中是可行的方法。不要让参考文献的生存时间超过他们的生活时间。

但这是一个相当广泛的问题:我可以想到十几个可能的解决方案 - 很难说出你的情况最好,因为你的例子非常通用。也许在你告诉我们更多信息之后我们可以更具体一些(可能是你要实现的一小部分功能)。

只看你帖子的标题:许多Rust项目使用docopt - AFAIK甚至cargo使用它。但是,我怀疑它对您的主要设计问题没有帮助。

此外:在new方法中,由于隐式返回,您可以删除一些代码。这样:

pub fn new(field: String) -> SomeStruct {
    let some_struct = SomeStruct {
        some_field: field,
    };
    return some_struct;
}

......变成了这个:

pub fn new(field: String) -> SomeStruct {
    SomeStruct {
        some_field: field,
    }
}

答案 1 :(得分:2)

我试图在 Rust 中做同样的事情,遵循设计模式一书中的例子。这里的问题是我们需要维护 Command trait 的通用接口,这意味着我们不应该将 trait 设计为采用某些类型的特定对象。我们剩下的唯一解决方案是在实现 Command 特征的对象的具体实例中存储对对象本身的可变引用。但是,如果我们使用 &'a mut ...,Rust 编译器将不喜欢对单个对象的多个可变引用,这意味着对于我们想要执行命令的任何给定对象,我们只能有一个 Command 实例,并且我们只能调用一次执行。

这可以通过使用 RefCellArc<Mutex<>> 来实现。我已经实现了它们,它们都运行良好。不同的是,RefCell 不是线程安全的,所以如果你选择以这种方式实现它,你就不能跨线程共享同一个 Command 对象;而 Arc<Mutex<>> 是线程安全的。

以下是我的实现:

trait Command {
    fn execute(&mut self);            // Execute command.
    fn is_reversible(&self) -> bool;  // Undoable operation?
    fn unexecute(&mut self);          // Undo command.
}

// ------ Using RefCell ------

struct ChangeFontSizeCommand<'a> {
    text: &'a RefCell<dyn Text>,
    old_size: Option<usize>,
    new_size: usize
}

impl<'a> Command for ChangeFontSizeCommand<'a> {
    // Implementation... (many calls to .borrow() and .borrow_mut())
}

impl<'a> ChangeFontSizeCommand<'a> {
    pub fn new(text: &'a RefCell<dyn Text>, new_size: usize) -> Self {
        // Implementation...
    }
}

// ------ Using Arc and Mutex ------

struct ChangeFontColorCommand {
    text: Arc<Mutex<dyn Text>>,
    old_color: Option<Color>,
    new_color: Color
}

impl Command for ChangeFontColorCommand {
    // Implementation... (many calls to .lock().unwrap())
}

impl ChangeFontColorCommand {
    pub fn new(text: Arc<Mutex<dyn Text>>, new_color: Color) -> Self {
        // Implementation...
    }
}

请注意,在两个示例中,RefCellArc<Mutex<>> 的实例必须在对象初始值设定项的外部创建,我们不能传入可变引用并在其内部创建它们命令实现结构,这将违反 Rust 的借用检查器规则。