Recherche…


Introduction

Rust est orienté objet en ce sens que ses types de données algébriques peuvent avoir des méthodes associées, ce qui les rend des objets au sens de données stockées avec du code qui sait les utiliser.

La rouille ne supporte cependant pas l'héritage, favorisant la composition avec des traits. Cela signifie que beaucoup de modèles OO ne fonctionnent pas tel quel et doivent être modifiés. Certains sont totalement hors de propos.

Héritage avec des traits

Dans Rust, il n'y a pas de concept d'héritage des propriétés d'une structure. Au lieu de cela, lorsque vous concevez la relation entre les objets, faites-la de manière à ce que sa fonctionnalité soit définie par une interface (une caractéristique de Rust). Cela favorise la composition par rapport à l'héritage , ce qui est considéré comme plus utile et plus facile à étendre à des projets plus importants.

Voici un exemple d'utilisation d'un exemple d'héritage dans Python:

class Animal:
    def speak(self):
        print("The " + self.animal_type + " said " + self.noise)

class Dog(Animal):
    def __init__(self):
        self.animal_type = 'dog'
        self.noise = 'woof'

Pour traduire cela en rouille, nous devons éliminer ce qui constitue un animal et le transformer en traits.

trait Speaks {
     fn speak(&self);

     fn noise(&self) -> &str;
}

trait Animal {
    fn animal_type(&self) -> &str;
}

struct Dog {}

impl Animal for Dog {
    fn animal_type(&self) -> &str {
        "dog"
    }
}  

impl Speaks for Dog {
    fn speak(&self) {
        println!("The dog said {}", self.noise());
    }

    fn noise(&self) -> &str {
        "woof"
    }
}

fn main() {
    let dog = Dog {};
    dog.speak();
}

Notez comment nous avons divisé cette classe parente en deux composants distincts: la partie qui définit la structure en tant qu’animal et la partie qui lui permet de parler.

Les lecteurs avertis remarqueront que ce n’est pas du tout un à un, car chaque implémenteur doit réimplémenter la logique pour imprimer une chaîne sous la forme "{animal} a dit {noise}". Vous pouvez le faire avec une légère refonte de l'interface où nous implémentons Speak for Animal :

trait Speaks {
     fn speak(&self);
}

trait Animal {
    fn animal_type(&self) -> &str;
    fn noise(&self) -> &str;
}

impl<T> Speaks for T where T: Animal {
    fn speak(&self) {
        println!("The {} said {}", self.animal_type(), self.noise());
    }
}

struct Dog {}
struct Cat {}

impl Animal for Dog {
    fn animal_type(&self) -> &str {
        "dog"
    }
    
    fn noise(&self) -> &str {
        "woof"
    }
}

impl Animal for Cat {
    fn animal_type(&self) -> &str {
        "cat"
    }

    fn noise(&self) -> &str {
        "meow"
    }
}

fn main() {
    let dog = Dog {};
    let cat = Cat {};
    dog.speak();
    cat.speak();
}

Remarquez maintenant que l'animal fait du bruit et parle simplement maintenant, il a une implémentation pour tout ce qui est un animal. C'est beaucoup plus flexible que la méthode précédente et l'héritage Python. Par exemple, si vous voulez ajouter un Human avec un son différent, nous pouvons simplement avoir une autre implémentation de speak pour quelque chose d' Human :

trait Human {
    fn name(&self) -> &str;
    fn sentence(&self) -> &str;
}

struct Person {}

impl<T> Speaks for T where T: Human {
    fn speak(&self) {
        println!("{} said {}", self.name(), self.sentence());
    }
}

Motif de visiteur

L'exemple typique de visiteur en Java serait:

interface ShapeVisitor {
    void visit(Circle c);
    void visit(Rectangle r);
}

interface Shape {
    void accept(ShapeVisitor sv);
}

class Circle implements Shape {
    private Point center;
    private double radius;

    public Circle(Point center, double radius) {
        this.center = center;
        this.radius = radius;
    }

    public Point getCenter() { return center; }
    public double getRadius() { return radius; }

    @Override
    public void accept(ShapeVisitor sv) {
        sv.visit(this);
    }
}

class Rectangle implements Shape {
    private Point lowerLeftCorner;
    private Point upperRightCorner;

    public Rectangle(Point lowerLeftCorner, Point upperRightCorner) {
        this.lowerLeftCorner = lowerLeftCorner;
        this.upperRightCorner = upperRightCorner;
    }

    public double length() { ... }
    public double width() { ... }

    @Override
    public void accept(ShapeVisitor sv) {
        sv.visit(this);
    }
}

class AreaCalculator implements ShapeVisitor {
    private double area = 0.0;

    public double getArea() { return area; }

    public void visit(Circle c) {
        area = Math.PI * c.radius() * c.radius();
    }

    public void visit(Rectangle r) {
         area = r.length() * r.width();
    }
}

double computeArea(Shape s) {
    AreaCalculator ac = new AreaCalculator();
    s.accept(ac);
    return ac.getArea();
}

Cela peut être facilement traduit par Rust, de deux manières.

La première méthode utilise le polymorphisme d'exécution:

trait ShapeVisitor {
    fn visit_circle(&mut self, c: &Circle);
    fn visit_rectangle(&mut self, r: &Rectangle);
}

trait Shape {
    fn accept(&self, sv: &mut ShapeVisitor);
}

struct Circle {
    center: Point,
    radius: f64,
}

struct Rectangle {
    lowerLeftCorner: Point,
    upperRightCorner: Point,
}

impl Shape for Circle {
    fn accept(&self, sv: &mut ShapeVisitor) {
        sv.visit_circle(self);
    }
}

impl Rectangle {
    fn length() -> double { ... }
    fn width() -> double { ... }
}

impl Shape for Rectangle {
    fn accept(&self, sv: &mut ShapeVisitor) {
        sv.visit_rectangle(self);
    }
}

fn computeArea(s: &Shape) -> f64 {
    struct AreaCalculator {
        area: f64,
    }

    impl ShapeVisitor for AreaCalculator {
        fn visit_circle(&mut self, c: &Circle) {
            self.area = std::f64::consts::PI * c.radius * c.radius;
        }
        fn visit_rectangle(&mut self, r: &Rectangle) {
            self.area = r.length() * r.width();
        }
    }
    
    let mut ac = AreaCalculator { area: 0.0 };
    s.accept(&mut ac);
    ac.area
}

La seconde méthode utilise à la place le polymorphisme à la compilation, seules les différences sont affichées ici:

trait Shape {
    fn accept<V: ShapeVisitor>(&self, sv: &mut V);
}

impl Shape for Circle {
    fn accept<V: ShapeVisitor>(&self, sv: &mut V) {
        // same body
    }
}

impl Shape for Rectangle {
    fn accept<V: ShapeVisitor>(&self, sv: &mut V) {
        // same body
    }
}

fn computeArea<S: Shape>(s: &S) -> f64 {
    // same body
}


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow