abstract 类 的 Rust 等价物是什么?

What is the Rust equivalent for abstract classes?

我仍然是 Rust 的初学者,我被困在一个关于特征的问题上。 尽管进行了多次尝试,但我找不到 rust 中抽象 类 的等价代码。 这是使用它们的 Typescript 代码示例:

export interface NodeConstructor<T> {
  new (data: T): Node<T>;
}

export abstract class Node<T> {
  public data: T;
  public key: string;
  public parentKeys: string[];
  public childKeys: string[];
  public parents: Node<T>[];
  public children: Node<T>[];

  constructor(data: T) {
    this.data = data;
    this.key = this.buildNodeKey();
    this.parentKeys = this.buildParentKeys();
    this.childKeys = this.buildChildKeys();
    this.parents = [];
    this.children = [];
  }

  get hasParents(): boolean {
    return !!this.parents.length;
  }

  get hasChildren(): boolean {
    return !!this.children.length;
  }

  abstract buildNodeKey(): string;

  abstract buildChildKeys(): string[];

  abstract buildParentKeys(): string[];
}

感谢 'mwlon' 中的解决方案 post,我得到了这个结果:

pub struct Node<T, BUILDER: ?Sized> where BUILDER: NodeBuilder {
    pub data: T,
    pub key: String,
    pub parent_keys: Box<[String]>,
    pub child_keys: Box<[String]>,
    pub parents: Option<Box<[T]>>,
    pub children: Option<Box<[T]>>,
    builder: BUILDER,
}

pub trait NodeBuilder {
    fn build_node_key(&self) -> String;
    fn build_parent_key(&self) -> Box<[String]>;
    fn build_child_key(&self) -> Box<[String]>;
}

impl<T , BUILDER> Node<T, BUILDER> where BUILDER: NodeBuilder {
    pub fn new(&self, data: T) -> Node<T, BUILDER> {
        Self{
            data: data,
            key: BUILDER::build_node_key(&self.builder),
            parent_keys: BUILDER::build_parent_key(&self.builder),
            child_keys: BUILDER::build_child_key(&self.builder),
            parents: None,
            children: None,
            builder: self.builder
        }
    }
    pub fn has_parents(&self) -> bool {
        match &self.parents {
            Some(_x) => true,
            None => false,
        }
    }
    pub fn has_children(&self) -> bool {
        match &self.children {
            Some(_x) => true,
            None => false,
        }
    }
}

哪个实现是这样的:

struct TestModel {
    name: String,
    children: Option<Box<[String]>>,
    parents: Option<Box<[String]>>
}
impl node::Node<TestModel, dyn node::NodeBuilder> {
    fn build_child_key(data: TestModel) -> Box<[String]> {
        match data.children {
            Some(x) => x.clone(),
            None => Box::new([]),
        }
    }
    fn build_node_key(data: TestModel) -> String {
        data.name.clone()
    }
    fn build_parent_key(data: TestModel) -> Box<[String]> {
        match data.parents {
            Some(x) => x.clone(),
            None => Box::new([]),
        }
    }
}

但是我还有一个错误无法克服:

cannot move out of `self.builder` which is behind a shared reference

move occurs because `self.builder` has type `BUILDER`, which does not implement the `Copy` traitrustc(E0507)
node.rs(28, 22): move occurs because `self.builder` has type `BUILDER`, which does not implement the `Copy` trait

我无法在 Builder 上实现 'Copy',因为它也是一个特征。有什么我想念的吗? Rust 中这种结构的最佳实践是什么? 我正在使用 rustc 1.59.0 (9d1b2106e 2022-02-23)

如果你真的想在 Rust 中复制抽象 classes,这是可能的。你走在正确的轨道上,有一个代表抽象方法的特征(NodeBuilder)和一个通用的 struct 特征(Node)。但是,所写示例存在一些问题。首先,你的构造函数。

  • Node::new方法不应该采用&self,因为它创建了一个新节点,它不会初始化已经存在的节点。与其他一些语言不同,Rust 没有对现有对象进行操作的构造函数。相反,对象创建是用类型上的自由函数表示的,通常称为 new。一些其他语言调用这些函数 'static.'
  • 为了创建一个节点,您将需要一个 BUILDER 的具体实例来存储在 self.builder 中。这可能应该是 Node::new 的一个参数。或者,如果您知道构建器本身永远不会携带状态,则可以将 NodeBuilder 特征上的方法设为静态并删除 builder 字段。

综合起来,这应该导致:

pub fn new(builder: BUILDER, data: T) -> Node<T, BUILDER> {
    Self {
        data: data,
        key: builder.build_node_key(),
        parent_keys: builder.build_parent_key(),
        child_keys: builder.build_child_key(),
        parents: None,
        children: None,
        builder: builder
    }
}

其次,实施class。

  • 该类型的所有'abstract'方法都封装在特征NodeBuilder中。因此,您不想在 node::Node<TestModel, dyn node::NodeBuilder> 上实现额外的关联函数,而是想在 TestModel:
  • 上实现 NodeBuilder
impl NodeBuilder for TestModel {
    fn build_child_key(&self) -> Box<[String]> {
        match self.children {
            Some(x) => x.clone(),
            None => Box::new([]),
        }
    }
    fn build_node_key(&self) -> String {
        self.name.clone()
    }
    fn build_parent_key(&self) -> Box<[String]> {
        match self.parents {
            Some(x) => x.clone(),
            None => Box::new([]),
        }
    }
}

此时,可以将TestModel的实例传递给Node::new(连同一段数据)创建一个Node,其抽象功能由[=实现26=].

一个抽象 class 将一段数据耦合在一起,该数据对于该类型的每个实例具有相同的形状,以及一组在该类型的不同实例之间可能不同的行为。这是 Rust 中一个相对不寻常的模式,因为 Rust 比其他语言更鼓励数据和行为的解耦。

您尝试做的事情的更地道的翻译可能是沿着这些路线。首先,我们创建一个类型,其中包含代表节点的 data

pub struct Node<T> {
    data: T,
    key: String,
    parent_keys: Box<[String]>,
    child_keys: Box<[String]>,
    parents: Option<Box<[T]>>,
    children: Option<Box<[T]>>,
}

我们需要一种方法来创建这种类型的实例,因此我们将给它一个构造函数。我不知道您打算如何填充 parentschildren 字段,所以我暂时将它们保留为 None。但是,如果要使用 parent_keyschild_keys 从外部源读取数据来填充它们,则此构造函数可能是执行此操作的正确位置。

impl<T> Node<T> {
    pub fn new(data: T, key: String, parent_keys: Box<[String]>, child_keys: Box<[String]>) -> Node<T> {
        Node { data, key, parent_keys, child_keys, parents: None, children: None }
    }
}

接下来,我们想要一个特征来抽象出可能的 行为 。在您的情况下,行为似乎是 'a way to create a node.' 特征应该具有实现其行为所必需的方法,因此:

pub trait NodeBuilder {
    fn build_node<T>(&self, data: T) -> Node<T>;
}

我们可以在方法或结构中使用受 NodeBuilder 限制的泛型参数来抽象能够 构建节点的类型。我们可以通过为它们实现 NodeBuilder 来定义哪些类型能够做到这一点,就像这样:

struct TestModel {
    name: String,
    children: Option<Box<[String]>>,
    parents: Option<Box<[String]>>
}

impl NodeBuilder for TestModel {
    fn build_node<T>(&self, data: T) -> Node<T> {
        let parents = self.parents.clone().unwrap_or_else(Default::default);
        let children = self.children.clone().unwrap_or_else(Default::default);
        Node::new(data, self.name.clone(), parents, children)
    }
}

如您所见,此解决方案避免了在不必要时耦合数据和行为。但是,它特定于您所拥有的特定情况。不同的抽象 class 可能会转化为一组不同的类型和特征。这在跨编程范式转换时很常见:语言 A 中的一种模式的作用可能由语言 B 中的多种模式来填补,反之亦然。

与其过分关注如何在 Rust 中复制 'abstract classes' 这样的模式,您应该问问自己该模式在 TypeScript 中解决了什么问题,并考虑如何最好地解决 Rust 中的相同问题,即使这意味着使用不同的模式。