如何在 Rust 中实现以下 OOP 模式?

How to implement the below OOP patterns in Rust?

提问人:Eleanor 提问时间:11/17/2023 最后编辑:E_net4Eleanor 更新时间:11/17/2023 访问量:111

问:

在阅读了 Rust 这本书之后,我仍然在思考 Rust 中以下 Python 代码的 OOP 实现:

from abc import abstractmethod

class Animal:
    def __init__(self, name, age):
        self.age = age
        self.name = name
        
    @abstractmethod
    def eat(self):
        pass
    
class Dog(Animal):
    
    def __init__(self, name, age, favorite_bone):
        super().__init__(name, age)
        self.favorite_bone=favorite_bone
    
    def eat(self):
        print(f"Eating {self.favorite_bone}")
        
dog = Dog("Tom", 1, "big bone")
dog.eat()

该方法可以通过为 实现的 来完成,但随后我也希望从字段的 OOP 继承中受益。(请注意,我举了一个非常简单的例子,但在实践中手头的问题有更多的领域和方法)。eattraitDognameage

这就是我到目前为止所做的:

trait Animal {
    fn new(name: String, age: u8) -> Self;
    fn eat(&self);
}

struct Dog {
    name: String,
    age: u8,
    favorite_bone: String,
}

impl Animal for Dog {
    fn new(name: String, age: u8) -> Dog {
        Dog {
            name,
            age,
            favorite_bone: String::new(),
        }
    }

    fn eat(&self) {
        println!("Eating {}", self.favorite_bone);
    }
}

fn main() {
    let dog = Dog {
        name: String::from("Tom"),
        age: 1,
        favorite_bone: String::from("big bone"),
    };

    dog.eat();
}

我的主要问题是,对于所有实现该特征的人,我需要重新定义方法,其中包含许多类似的设置逻辑,(并且实际上还有更多字段)。有没有更好的方法?structAnimalnewnameage

oop 继承 rust

评论

7赞 FZs 11/17/2023
我无法给你“正确”的方式将你的代码翻译成 Rust,因为根据实际需求,有几种方法可以表达它。你不应该尝试在 Rust 中编写 OOP,你应该尝试在 Rust 中解决你的问题。综上所述,如果你从一个结构体开始,然后 .但是,我不会使用特质,而是特质,因为特质并不关心一个事物是什么,它们关心的是它能做什么。如果一个结构可以做几件事,那就创建更多的特征。Animalstruct Dog{ animal: Animal, favorite_bone: String }AnimalEater

答:

1赞 prog-fh 11/17/2023 #1

这个想法是更喜欢组合而不是继承(不仅在 Rust 中)。 结构包含要实现的概念的公共部分,以及指向动态部分的一个(或多个)指针,该部分可以以 OOP 方式更改。 这样的动态部分通常是实现接口的盒装对象(Rust 中的一个特征)。

在下面的示例中,我决定让该方法使动物发生变异,以说明微妙之处。 事实上,我们不能调用,因为该参数是独占引用,尽管它在调用开始时使用。 该技巧依赖于对保存动态行为的选项的获取/重新分配序列。.eat()self.eating_behaviour.eat(self)

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

pub mod abstract_level {

    pub struct Animal {
        name: String,
        weight: u8,
        eating_behaviour: Option<Box<dyn EatingBehaviour>>,
    }
    impl Animal {
        pub fn new(
            name: String,
            weight: u8,
            eating_behaviour: Box<dyn EatingBehaviour>,
        ) -> Self {
            Self {
                name,
                weight,
                eating_behaviour: Some(eating_behaviour),
            }
        }
        pub fn name(&self) -> &str {
            &self.name
        }
        pub fn weight(&self) -> u8 {
            self.weight
        }
        pub fn increase_weight(
            &mut self,
            delta: u8,
        ) {
            self.weight += delta
        }
        pub fn eat(&mut self) {
            // take/reassing in or dor to avoid multiple exclusive refs
            if let Some(eating_behaviour) = self.eating_behaviour.take() {
                eating_behaviour.eat(self);
                self.eating_behaviour = Some(eating_behaviour);
            }
        }
    }

    pub trait EatingBehaviour {
        fn eat(
            &self,
            me: &mut Animal,
        );
    }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

pub mod dog_specific {
    use super::abstract_level::{Animal, EatingBehaviour};

    struct DogEatingBehaviour {
        favorite_bone: String,
    }
    impl DogEatingBehaviour {
        fn new(favorite_bone: String) -> Self {
            Self { favorite_bone }
        }
    }
    impl EatingBehaviour for DogEatingBehaviour {
        fn eat(
            &self,
            me: &mut Animal,
        ) {
            println!("dog eating {}", self.favorite_bone);
            me.increase_weight(2);
        }
    }

    // for convenience
    pub fn make_dog(
        name: String,
        weight: u8,
        favorite_bone: String,
    ) -> Animal {
        Animal::new(
            name,
            weight,
            Box::new(DogEatingBehaviour::new(favorite_bone)),
        )
    }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

pub mod cat_specific {
    use super::abstract_level::{Animal, EatingBehaviour};

    struct CatEatingBehaviour {}
    impl CatEatingBehaviour {
        fn new() -> Self {
            Self {}
        }
    }
    impl EatingBehaviour for CatEatingBehaviour {
        fn eat(
            &self,
            me: &mut Animal,
        ) {
            println!("cat eating");
            me.increase_weight(1);
        }
    }

    // for convenience
    pub fn make_cat(
        name: String,
        weight: u8,
    ) -> Animal {
        Animal::new(name, weight, Box::new(CatEatingBehaviour::new()))
    }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

fn main() {
    let mut animals = [
        dog_specific::make_dog("Tom".to_owned(), 10, "big bone".to_owned()),
        cat_specific::make_cat("Hop".to_owned(), 3),
    ];
    for a in animals.iter_mut() {
        println!("~~~~~~~~");
        println!("{}, {} kg", a.name(), a.weight());
        a.eat();
        println!("{}, {} kg", a.name(), a.weight());
    }
}
/*
~~~~~~~~
Tom, 10 kg
dog eating big bone
Tom, 12 kg
~~~~~~~~
Hop, 3 kg
cat eating
Hop, 4 kg
*/
0赞 Wiktor Zychla 11/17/2023 #2

虽然结构之间不能有继承,但你可以让结构实现相同的特征。由于您的 Python 代码包含两个类,因此您需要两个结构体和两个类的特征,而不仅仅是一个特征/结构,就像您的实现一样。

若要从类借用实现中获益,只需委托而不是继承

有多种方法可以做到这一点,无论你做什么,都要确保你与 Rust 关于所有者实例和拥有实例之间生命周期依赖关系的要求同步。

但只是为了给你一个接近你的 Python 代码的例子:

trait Creature {
    fn eat(&self) -> ();
}

struct Animal {
    age: i32
}

impl Animal {
    fn new(age: i32) -> Self {
        Self { age }
    }
}

impl Creature for Animal {
    fn eat(&self) {
        println!("Animal::eats, age: {:?}", self.age );        
    }
}

struct Dog {
    a: Animal,
    bone: i32
}

impl Dog {
    fn new(age: i32, bone: i32) -> Self {
        Self { a: Animal::new(age), bone }
    }
}

impl Creature for Dog {
    fn eat(&self) {
        println!("Dog::eats, age: {:?}, bone: {:?}", self.a.age, self.bone );        
    }
}

fn main() {
    let animal = Animal::new(1);
    animal.eat();

    let dog = Dog::new(1, 6);
    dog.eat();
}

请注意,这里唯一的委托是针对数据(委托给 ),但如果 不仅打印,而且返回某些内容,则委托结构 () 可以使用它委托调用的实例,调用方法并在自己的环境中使用它。ageaeatDogeat