如何避免语义相等 fields/properties 的不同结构的代码重复?

How to avoid code duplication of different structs with semantically equal fields/properties?

给定这两个结构:

pub struct RectangleRenderer {
    canvas: Canvas,
    origin: Point,
    shape: Rectangle,
}

pub struct CircleRenderer {
    canvas: Canvas,
    center: Point,
    shape: Circle,
}

因为我来自 Java,所以我会从中提取基数 class ShapeRenderer 并将字段 canvasorigin 应用到其中而特定类型将保留其名为 shape 的字段。对于这种情况,Rust 中的最佳实践是什么,因为特征仅类似于接口,因此不允许 properties/fields?

这看起来是 generics 的完美案例。

你可以像这样创建一个结构:

struct ShapeRenderer<T: Shape> {
    canvas: Canvas,
    origin: Point,
    shape: T,
}

请注意,我已通过特征 Shape(您必须创建)来限制泛型类型 T。你可以在这里设置任何你喜欢的界限(或者根本没有界限),但是你将被限制使用这些特征的成员。

您希望能够在您的形状中访问的任何内容都需要由 Shape 公开。例如,如果您需要中心和区域,那么特征定义可能如下所示:

trait Shape {
    fn center(&self) -> (f64, f64);
    fn area(&self) -> f64;
}

如果这不够灵活,您还可以为特定形状提供 ShapeRenderer 特殊行为。例如:

impl ShapeRenderer<Rectangle> {
    fn n_sides(&self) -> u32 {
        4
    }
}

请注意,在 impl 中,我们可以访问 Rectangle 的所有字段,而不仅仅是 Shape.

中的函数

或者,您可以创建一个基本结构,然后将其作为最终结构的成员包含在内:

struct Renderer {
    canvas: Canvas,
    origin: Point,
}

struct CircleRenderer {
    renderer: Renderer,
    shape: Circle,
}

struct RectangleRenderer {
    renderer: Renderer,
    shape: Rectangle,
}

这是 Rust 中最接近标准继承的东西。


第三,如果您在创建这些结构时只关心代码重复并且不希望它们共享字段以外的任何内容,您可以使用宏:

macro_rules! make_renderer {
    ($name: ty, $shape: ty) => (
        struct $name {
            canvas: Canvas,
            origin: Point,
            shape: $shape,
        }
    );
}

make_renderer!(CircleRenderer, Circle);
make_renderer!(RectangleRenderer, Rectangle);

虽然泛型示例是最复杂的,但它也是最强大和最灵活的。它使您可以轻松地在结构之间共享代码,同时还可以让您拥有特定于一个结构的代码,使您可以访问其所有字段。