2016-09-10 1 views
1

Учитывая эти два Структуры:Как избежать дублирования кода разных структур с семантически равными полями/свойствами?

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

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

Как я из Java, я бы извлечь базовый класс ShapeRenderer из тех, и применять на полях canvas и origin в том, что в то время как конкретные типы будут держать их поле под названием shape. Какова наилучшая практика в Rust для этой ситуации, поскольку черты действуют только с интерфейсами и поэтому не позволяют свойства/поля?

+0

Я нашел аналогичный вопрос, где ответ рассказывает о композиции -> http://stackoverflow.com/questions/32736170/what-is-the-best-way-to-inherit-a-struct-in-ust- 1-3. – xetra11

+2

В качестве альтернативы мы могли бы использовать 'struct Renderer '. – kennytm

+0

@kennytm Я бы не сказал, что это «альтернатива» - это * * состав! – Shepmaster

ответ

3

Это выглядит как идеальный случай для generics.

Вы можете сделать одну-структуру, как это:

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

Обратите внимание, что я ограниченный общий тип T по признаку Shape (что вы должны создать). Вы можете поместить любые границы, которые вам нравятся (или вообще не имеют границ), но вы будете ограничены использованием этих признаков.

Все, что вы хотите иметь доступ к своим фигурам, должно быть выставлено 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, 
} 

Это самая близкая вещь в Русте к стандартному наследству.


В-третьих, если вы заботитесь только о дублировании кода при создании этих структур и не хочу им делиться ничего, кроме полей, вы можете использовать макрос:

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

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

В то время как пример обобщений является самым сложным, он также является самым мощным и гибким. Это позволяет вам легко распределять код между вашими структурами, а также позволять вам иметь код, специфичный для одного, который дает вам доступ ко всем его полям.

+0

На самом деле я больше искал что-то, что только уменьшает дублирование кода для самих полей. Подобно тому, как имеет структуру 'ShapeRenderer', которая содержит поле' canvas' и 'origin', а затем одна конкретная структура, называемая' RectangleRenderer', которая содержит поле 'shape', но также * наследует * поля' ShapeRenderer' - подобно Наследование Java 'RectangleRenderer расширяет ShapeRenderer'. Черты дают мне возможность абстрагировать функции/методы, но не поля. – xetra11

+0

@ xetra11 вы также можете использовать макросы для уменьшения дублирования кода https://users.rust-lang.org/t/best-way-to-reuse-partial-trait-implementation/6840/1 – breeden

+0

@ xetra11 Это уменьшает дублирование , Я думаю, вы просто привыкли к наследованию стиля Java, но это вопрос предпочтения; лично я очень рад, что у Рюста этого нет. – ljedrz

Смежные вопросы