【问题标题】:How to achieve behavior similar to @override in Rust如何在 Rust 中实现类似于 @override 的行为
【发布时间】:2020-12-06 17:42:11
【问题描述】:

我是 rust 新手,目前正在重写我的一些旧 Java 代码。这是我第一次不使用 OOP 编程,所以这对我来说既陌生又陌生。

我在理解如何为结构的每个实例实现不同的方法(具有相同的名称)时遇到了一些问题。本质上,我试图在 Java 中实现类似于 abstract Classextends@override 的行为。

也许这个例子能更好地解释我到底想要做什么。在其中,我尝试对 AbstractNode 的每个实例实现不同的 execute() 逻辑。

创建一个名为“AbstractNode”的结构,其中包含一些数据并具有与之关联的 3 个方法(validate()、log()、execute())

struct AbstractNode {
    pub data: //data here
    pub validate: bool,
    pub log: String,
} 

trait NodeFunctions {
    fn validate(&self)->bool{false}
    fn log(&self){println!("/")}
    fn execute(&self){}
}

impl NodeFunctions for AbstractNode{
    fn validate(&self)->bool{
        self.validate
    }
    fn log(&self){
        println!("{}/", self.log);
    }
    fn execute(&self){
        //--this function is the problem, because I don't want its behavior to be 
        //shared between all instances of Abstract node--
    }
}

然后我实例化几个节点。 如果可能的话,我还想在这里的某个地方定义 execute() 的主体。

let node1 = AbstractNode{
    data: //data
    validate: false,
    log: "node1".to_string(),
};

let node2 = AbstractNode{
    data: //data
    validate: 1>0,
    log: "node2".to_string(),
};

let node3 = AbstractNode{
    data: //data
    validate: true,
    log: "node3".to_string(),
};
//...

它像这样从 main 调用。如果 validate() 中的条件为真,则首先执行 log() 方法,这对所有节点都是相同的。然后是不是所有节点都相同的execute()。

fn main(){
    let mut node_tree = vec![
        node1,
        node2,
        node3
        //...
    ];

    for node in node_tree.iter() {
        if node.validate(){
            node.log();
            node.execute(); //<--
            break;
        }
    };
}

每个节点应该能够在 execute() 方法下保存不同的逻辑,我不知道如何定义这个特定的行为。

我希望这个问题足够清楚。如果您不明白我要达到的目标,请提出其他问题。

Ty 提前。

【问题讨论】:

  • 我正要说specialization。但是您似乎希望对同一类型的不同实例有不同的实现。一种方法是使用闭包,因此execute 将是Fn/Box&lt;dyn Fn&gt; 中的AbstractNode。但是,对于新类型可能会更好,例如struct SomeNode(AbstractNode),然后是impl NodeFunctions
  • 我希望所有节点的类型保持相同。不过,我会更多地研究闭包。泰
  • 听起来你在使用 Java 的 匿名类(?)Rust 并没有类似的机制(除了可能使用一些宏魔法)。使用Fn 可能是您想要的最简单的方法。
  • Java 不支持按实例覆盖方法。也许您可以发布您尝试移植的 Java 代码以使您的问题更清楚?
  • 嘿,我没有在 Java 中对每个实例进行覆盖,只是使用 'extends' 和 '@override' 将抽象类扩展到新类。我想在 Rust 中创建类似的工作流,每个实例都覆盖。

标签: rust


【解决方案1】:

您可以在某种程度上使用闭包来复制它。但是,如果您还希望能够在闭包内对其进行变异,您仍然会得到不能根据Node 通用的部分。

我已经重命名并删除了一些部分,以简化示例。

首先,您需要一个 NodeData 类型,用于保存您的数据。我假设您希望能够在“execute”方法中对其进行变异。然后你需要一个 Node 类型,它保存数据,以及该 Node 实例的盒装闭包。

struct NodeData {}

struct Node {
    data: NodeData,
    f: Box<dyn Fn(&mut NodeData)>,
}

然后我们将实现一个创建Node 实例的方法,以及调用闭包的execute 方法。

这就是使用闭包的限制出现的地方。您不能将对 Node 本身的可变引用传递给它。因为当您访问 self.f 调用闭包时,Node 会被借用。

impl Node {
    fn with<F>(f: F) -> Self
    where
        F: Fn(&mut NodeData) + 'static,
    {
        Self {
            data: NodeData {},
            f: Box::new(f),
        }
    }

    fn execute(&mut self) {
        (self.f)(&mut self.data);
    }
}

使用它的示例如下所示:

let mut nodes: Vec<Node> = vec![];

nodes.push(Node::with(|_node_data| {
    println!("I'm a node");
}));
nodes.push(Node::with(|_node_data| {
    println!("I'm another node");
}));
nodes.push(Node::with(|_node_data| {
    println!("I'm also a node");
}));

for node in &mut nodes {
    node.execute();
}

现在,这个工作。但是NodeData 不能是通用的,因为这样修改闭包中的数据变得越来越困难。

当然,您可以推迟将NodeData 设为HashMap,这样您就可以使用String 键和一些enum 值存储任何内容


虽然您不想拥有单独的类型。这确实让事情变得更容易了,因为所有节点类型都可以有不同种类的数据。

因为现在我们可以有一个 trait Node,它具有 execute 方法。

trait Node {
    fn execute(&mut self);
}

现在定义多个类型并为每个类型实现Node。同样,使用 trait 代替闭包的两个好处是:

  1. 您定义的每个节点都可以包含您想要的任何类型的数据
  2. 在这种情况下,execute 实际上能够修改 Self,而闭包解决方案不能。
struct NodeA {}
struct NodeB {}
struct NodeC {}

impl Node for NodeA {
    fn execute(&mut self) {
        println!("I'm a node");
    }
}

impl Node for NodeB {
    fn execute(&mut self) {
        println!("I'm another node");
    }
}

impl Node for NodeC {
    fn execute(&mut self) {
        println!("I'm also a node");
    }
}

您仍然可以拥有一个 Vecnodes,因为这些特征很容易被装箱。

let mut nodes: Vec<Box<dyn Node>> = vec![];

nodes.push(Box::new(NodeA {}));
nodes.push(Box::new(NodeB {}));
nodes.push(Box::new(NodeC {}));

for node in &mut nodes {
    node.execute();
}

【讨论】:

    【解决方案2】:

    您可以让AbstractNode 存储一个闭包,该闭包引用Self

    struct AbstractNode {
        pub validate: bool,
        pub log: String,
        pub executor: Box<dyn Fn(&Self)>
    }
    

    AbstractNodeNodeFunctions 实现将简单地调用 executor 闭包:

    impl NodeFunctions for AbstractNode {
        fn execute(&self) {
            (self.executor)(&self)
        }
        // ...
    }
    

    现在,每次创建 AbstractNode 的新实例时,您都可以拥有自定义的 executor 函数。 executor 引用了self,因此可以访问节点的数据:

    let node1 = AbstractNode {
      validate: false,
      log: "node1".to_string(),
      executor: Box::new(|n| println!("Executing node #1. Is Valid? {}", n.validate))
    };
    
    // => Executing node #1. Is Valid? true
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-04
      • 1970-01-01
      • 1970-01-01
      • 2014-10-03
      • 2022-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多