-1

我知道我应该使用组合而不是继承。

在 Java/C++ 中,我有一个抽象基类Vehicle(带有属性和常用方法)和实现它的类,例如CarBike.

我发现enum_dispatch,它在将方法调用从父级转发到子“类”方面做得很好。

#[enum_dispatch]
pub trait VehicleDelegation {
    fn drive(&mut self) -> anyhow::Result<()>;
}

#[enum_dispatch(VehicleDelegation)]
pub enum Vehicle {
    Car,
    Bike,
}

pub struct Car {
    pub doors: u8
}

impl VehicleDelegation for Car {
    fn drive(&mut self) -> anyhow::Result<()> {
        // do car stuff
        Ok(())
    }
}

pub struct Bike {
    pub frame_size: u8
}

impl VehicleDelegation for Bike {
    fn drive(&mut self) -> anyhow::Result<()> {
        // do bike stuff
        Ok(())
    }
}
  • 我应该在哪里存储公共数据,例如num_wheels哪些是 的属性Vehicle
  • 我应该在哪里定义Vehicle使用公共数据的方法?

目前,我必须将所有Vehicle数据和方法复制到每个枚举变体中,这越来越令人厌烦,因为它随着“方法”的数量和“类”的数量而扩展。

  • 我如何在 Rust 中惯用地做到这一点?
4

1 回答 1

1

enum_dispatch是一个实现非常具体的优化的板条箱,即当 trait 实现的数量很少并且预先知道时避免基于 vtable 的动态调度。在了解特征如何正常工作之前,可能不应该使用它。

在 Rust 中,您从一个 trait 开始,例如Vehicle,它大致相当于 Java 接口:

pub trait Vehicle {
    fn drive(&mut self) -> anyhow::Result<()>;
}

你可以在任何你想要的类型上实现这个特性。如果他们需要一个通用的“基本类型”,您可以创建一个:

// Car and Bike can but don't have to actually use BaseVehicle in their
// implementations. As long as they implement the Vehicle trait, they're fine.

struct BaseVehicle {
    num_wheels: u8,
}

struct Car {
    base_vehicle: BaseVehicle,
    doors: u8,
}

impl Vehicle for Car {
    fn drive(&mut self) -> anyhow::Result<()> {
        // do car stuff
        Ok(())
    }
}

struct Bike {
    base_vehicle: BaseVehicle,
    frame_size: u8,
}

impl Vehicle for Bike {
    fn drive(&mut self) -> anyhow::Result<()> {
        // do bike stuff
        Ok(())
    }
}

当一个函数需要接受任何车辆时,它会接受&mut dyn VehicleBox<dyn Vehicle>(取决于它是需要借用还是接管车辆的所有权)。

要回答您的问题:

  • 我应该在哪里存储公共数据,例如 num_wheels 这本来是 Vehicle 的属性?

只要它适合您的程序。如上所示,典型的方法是将它们放在所有车辆都包含通过组合的“基础”或“通用”类型中。num_wheels例如,可用作self.common_data.num_wheels

  • 我应该在哪里定义使用公共数据的 Vehicle 方法?

同样,它们将在通用类型上定义,它们可以被特定类型访问,并且(如果需要)通过它们的 trait 实现公开。

如果“基础”类型足够丰富,它本身就可以实现特征——在这种情况下,这意味着提供一个impl Vehicle for BaseVehicle. 然后Vehicle,具体类型的实现可以将它们的方法实现转发到self.base_vehicle.method(),这相当于super()调用。这种方法增加了很多样板,所以我不会推荐它,除非它真的有意义,即除非它BaseVehicle实际上提供了一个连贯且有用的Vehicle.

于 2021-05-09T07:29:01.717 回答