特质贬低

Pri*_*rgo 2 traits downcast rust

如何将特征向下转换为结构,就像这个 C# 示例中那样?

我有一个基本特征和几个派生结构,必须将它们推入单个基本特征向量中。

我必须检查向量的每个项目是否可转换为特定的派生结构,如果是,则将其用作该类型的结构。

这是我的 Rust 代码,我不知道如何实现注释部分。

trait Base {
    fn base_method(&self);
}

struct Derived1;
impl Derived1 {
    pub fn derived1_method(&self) {
        println!("Derived1");
    }
}
impl Base for Derived1 {
    fn base_method(&self) {
        println!("Base Derived1");
    }
}

struct Derived2;
impl Derived2 {
    pub fn derived2_method(&self) {
        println!("Derived2");
    }
}
impl Base for Derived2 {
    fn base_method(&self) {
        println!("Base Derived2");
    }
}

fn main() {
    let mut bases = Vec::<Box<dyn Base>>::new();
    let der1 = Derived1{};
    let der2 = Derived2{};

    bases.push(Box::new(der1));
    bases.push(Box::new(der2));

    for b in bases {
        b.base_method();
        //if (b is Derived1)
        //  (b as Derived1).derived1_method();
        //else if (b is Derived2)
        //  (b as Derived2).derived2_method();
    }
}
Run Code Online (Sandbox Code Playgroud)

Lag*_*aer 6

从技术上讲,您可以使用as_any,如本答案中所述:

如何从特征对象获取对具体类型的引用?

然而,在循环特征对象向量时进行类型检查和向下转型被认为是代码味道。如果将一堆对象放入一个向量中,然后循环该向量,那么该向量中的对象大概应该扮演类似的角色

因此,您应该重构代码,以便可以在对象上调用相同的方法,而不管底层的具体类型如何。

从您的代码来看,您似乎纯粹是在检查类型(和向下转型),以便可以调用适当的方法。那么,您真正应该做的是引入另一个特征,该特征提供了一个统一的接口,然后您可以从循环中调用该接口,以便循环根本不需要知道具体类型。

编辑:请允许我添加一个具体的例子来强调这一点,但我将使用 Python 来展示这一点,因为在 Python 中,很容易做你要求做的事情,所以我们可以专注于为什么它不是最佳设计选择

class Dog:
  def bark():
    print("Woof woof")

class Cat:
  def meow():
    print("Meow meow")

list_of_animals = [Dog(), Dog(), Cat(), Dog()]

for animal in list_of_animals:
  if isinstance(animal, Dog):
    animal.bark()
  elif isinstance(animal, Cat):
    animal.meow()
Run Code Online (Sandbox Code Playgroud)

这里,Python 的动态类型允许我们将所有对象放入同一个列表中,对其进行迭代,然后在运行时找出类型,以便我们可以调用正确的方法。

但实际上,设计良好的面向对象代码的全部要点是将调用者的负担减轻给对象。这种类型的设计非常不灵活,因为一旦您添加另一种动物,您就必须向您的if块添加另一个分支,并且您最好在有该分支的任何地方都这样做。

解决方案当然是确定两者所扮演的共同角色,并将该行为抽象到接口中。当然,在Python中我们不需要正式声明这样的接口,所以我们可以在其中添加另一个方法:barkmeow

class Dog:
  ...
  def make_sound():
    self.bark()

class Cat:
  ...
  def make_sound():
    self.meow()

...

for animal in list_of_animals:
  animal.make_sound()
Run Code Online (Sandbox Code Playgroud)

在 Rust 代码中,您实际上有两个选择,这取决于您设计的其余部分。或者,正如我所建议的,添加另一个特征来表达对象所扮演的共同角色(为什么首先将它们放入向量中?)并为所有派生结构实现该特征。或者,将所有各种派生结构表示为同一枚举的不同变体,然后向处理调度的枚举添加一个方法。当然,枚举比使用特征版本更接近外部扩展。这就是为什么解决方案将取决于您的需求。