性格低落

Trait downcasting

如何将特征向下转换为结构,如 this C# example

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

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

This is my Rust code, 注释的部分不知道怎么实现

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();
    }
}

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

但是,在遍历特征对象向量时进行类型检查和向下转换被认为是一种代码味道。如果将一堆对象放入一个向量中,然后循环遍历该向量,则该向量中的对象大概应该扮演类似的 role.

那么你应该重构你的代码,这样你就可以在你的对象上调用相同的方法,而不管底层的具体类型如何。

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

编辑:请允许我添加一个突出显示这一点的具体示例,但我将使用 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()

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

但实际上,设计良好的面向对象代码的全部意义在于将调用者的负担减轻到对象。这种类型的设计非常不灵活,因为一旦添加另一种动物,就必须在 if 块中添加另一个分支,最好在 everywhere你有那个分支。

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

中再打一个方法
class Dog:
  ...
  def make_sound():
    self.bark()

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

...

for animal in list_of_animals:
  animal.make_sound()

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