【问题标题】:Trait downcasting特征向下转换
【发布时间】:2021-09-08 17:29:46
【问题描述】:

如何将特征向下转换为 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();
    }
}

【问题讨论】:

    标签: rust traits downcast


    【解决方案1】:

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

    How to get a reference to a concrete type from a trait object?

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

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

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

    编辑:请允许我添加一个具体示例来突出显示这一点,但我将使用 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 块中,并且您最好在有该分支的任何地方都这样做。

    解决方案当然是识别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 添加一个方法。枚举当然比使用 trait 版本更接近外部扩展。这就是解决方案将取决于您的需求的原因。

    【讨论】:

    • "枚举当然比使用 trait 版本更封闭外部扩展。"——也许值得注意的是权衡的另一面:即枚举不会受到特征对象间接的成本。
    • 当然。感谢您指出这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多