Caractéristiques des langages orientés objet

RustRustBeginner
Pratiquer maintenant

This tutorial is from open-source community. Access the source code

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Bienvenue dans Caractéristiques des langages orientés objet. Ce laboratoire est une partie du Rust Book. Vous pouvez pratiquer vos compétences Rust dans LabEx.

Dans ce laboratoire, nous allons explorer les caractéristiques des langages orientés objet, y compris les objets, l'encapsulation et l'héritage, et examiner si Rust prend en charge ces fonctionnalités.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust(("Rust")) -.-> rust/AdvancedTopicsGroup(["Advanced Topics"]) rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/DataTypesGroup -.-> rust/integer_types("Integer Types") rust/DataTypesGroup -.-> rust/floating_types("Floating-point Types") rust/DataTypesGroup -.-> rust/type_casting("Type Conversion and Casting") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") rust/AdvancedTopicsGroup -.-> rust/traits("Traits") rust/AdvancedTopicsGroup -.-> rust/operator_overloading("Traits for Operator Overloading") subgraph Lab Skills rust/variable_declarations -.-> lab-100441{{"Caractéristiques des langages orientés objet"}} rust/integer_types -.-> lab-100441{{"Caractéristiques des langages orientés objet"}} rust/floating_types -.-> lab-100441{{"Caractéristiques des langages orientés objet"}} rust/type_casting -.-> lab-100441{{"Caractéristiques des langages orientés objet"}} rust/function_syntax -.-> lab-100441{{"Caractéristiques des langages orientés objet"}} rust/expressions_statements -.-> lab-100441{{"Caractéristiques des langages orientés objet"}} rust/method_syntax -.-> lab-100441{{"Caractéristiques des langages orientés objet"}} rust/traits -.-> lab-100441{{"Caractéristiques des langages orientés objet"}} rust/operator_overloading -.-> lab-100441{{"Caractéristiques des langages orientés objet"}} end

Caractéristiques des langages orientés objet

Il n'y a pas de consensus dans la communauté de programmation quant aux fonctionnalités qu'un langage doit avoir pour être considéré comme orienté objet. Rust est influencé par de nombreux paradigmes de programmation, y compris la programmation orientée objet (POO); par exemple, nous avons exploré les fonctionnalités issues de la programmation fonctionnelle au chapitre 13. On peut dire que les langages POO partagent certaines caractéristiques communes, à savoir les objets, l'encapsulation et l'héritage. Regardons ce que signifie chacune de ces caractéristiques et si Rust les prend en charge.

Les objets contiennent des données et un comportement

Le livre Design Patterns: Elements of Reusable Object-Oriented Software d'Erich Gamma, Richard Helm, Ralph Johnson et John Vlissides (Addison-Wesley, 1994), surnommé le livre des Gang of Four, est un catalogue de modèles de conception orientés objet. Il définit la programmation orientée objet de la manière suivante :

Les programmes orientés objet sont composés d'objets. Un objet regroupe à la fois des données et les procédures qui opèrent sur ces données. Les procédures sont généralement appelées méthodes ou opérations.

En utilisant cette définition, Rust est orienté objet : les structs et les enums ont des données, et les blocs impl fournissent des méthodes sur les structs et les enums. Même si les structs et les enums avec des méthodes ne sont pas appelés des objets, ils fournissent la même fonctionnalité, selon la définition des objets des Gang of Four.

L'encapsulation qui cache les détails d'implémentation

Un autre aspect généralement associé à la programmation orientée objet est l'idée d'encapsulation, qui signifie que les détails d'implémentation d'un objet ne sont pas accessibles au code utilisant cet objet. Par conséquent, la seule manière d'interagir avec un objet est à travers son API publique; le code utilisant l'objet ne devrait pas être en mesure de pénétrer dans les internaux de l'objet et de modifier directement les données ou le comportement. Cela permet au programmeur de modifier et de refactoriser les internaux d'un objet sans avoir besoin de modifier le code qui utilise l'objet.

Nous avons discuté de la manière de contrôler l'encapsulation au chapitre 7 : nous pouvons utiliser le mot clé pub pour décider quels modules, types, fonctions et méthodes de notre code devraient être publiques, et par défaut tout le reste est privé. Par exemple, nous pouvons définir un struct AveragedCollection qui a un champ contenant un vecteur de valeurs de type i32. Le struct peut également avoir un champ qui contient la moyenne des valeurs dans le vecteur, ce qui signifie que la moyenne n'a pas besoin d'être calculée à la demande chaque fois que quelqu'un en a besoin. En d'autres termes, AveragedCollection va cacher la moyenne calculée pour nous. Le Listing 17-1 contient la définition du struct AveragedCollection.

Nom de fichier : src/lib.rs

pub struct AveragedCollection {
    list: Vec<i32>,
    average: f64,
}

Listing 17-1 : Un struct AveragedCollection qui maintient une liste d'entiers et la moyenne des éléments de la collection

Le struct est marqué pub afin que d'autres codes puissent l'utiliser, mais les champs à l'intérieur du struct restent privés. Cela est important dans ce cas car nous voulons nous assurer que chaque fois qu'une valeur est ajoutée ou supprimée de la liste, la moyenne est également mise à jour. Nous le faisons en implémentant les méthodes add, remove et average sur le struct, comme montré dans le Listing 17-2.

Nom de fichier : src/lib.rs

impl AveragedCollection {
    pub fn add(&mut self, value: i32) {
        self.list.push(value);
        self.update_average();
    }

    pub fn remove(&mut self) -> Option<i32> {
        let result = self.list.pop();
        match result {
            Some(value) => {
                self.update_average();
                Some(value)
            }
            None => None,
        }
    }

    pub fn average(&self) -> f64 {
        self.average
    }

    fn update_average(&mut self) {
        let total: i32 = self.list.iter().sum();
        self.average = total as f64 / self.list.len() as f64;
    }
}

Listing 17-2 : Implémentations des méthodes publiques add, remove et average sur AveragedCollection

Les méthodes publiques add, remove et average sont les seules façons d'accéder ou de modifier les données dans une instance de AveragedCollection. Lorsqu'un élément est ajouté à list en utilisant la méthode add ou supprimé en utilisant la méthode remove, les implémentations de chaque appel appellent la méthode privée update_average qui s'occupe également de la mise à jour du champ average.

Nous laissons les champs list et average privés de sorte qu'il n'y ait aucun moyen pour le code externe d'ajouter ou de supprimer des éléments du champ list directement; sinon, le champ average pourrait être désynchronisé lorsque list change. La méthode average renvoie la valeur dans le champ average, permettant au code externe de lire average mais pas de le modifier.

Parce que nous avons encapsulé les détails d'implémentation du struct AveragedCollection, nous pouvons facilement modifier des aspects, tels que la structure de données, plus tard. Par exemple, nous pourrions utiliser un HashSet<i32> au lieu d'un Vec<i32> pour le champ list. Tant que les signatures des méthodes publiques add, remove et average restent les mêmes, le code utilisant AveragedCollection n'aura pas besoin de changer. Si nous avions rendu list public au lieu de cela, ce ne serait pas nécessairement le cas : HashSet<i32> et Vec<i32> ont des méthodes différentes pour ajouter et supprimer des éléments, donc le code externe devrait probablement changer s'il modifiait directement list.

Si l'encapsulation est un aspect requis pour qu'un langage soit considéré comme orienté objet, alors Rust répond à cette exigence. L'option d'utiliser ou non pub pour différentes parties du code permet d'encapsuler les détails d'implémentation.

L'héritage comme système de types et comme partage de code

L'héritage est un mécanisme grâce auquel un objet peut hériter d'éléments de la définition d'un autre objet, gagnant ainsi les données et le comportement de l'objet parent sans avoir à les définir à nouveau.

Si un langage doit avoir l'héritage pour être orienté objet, alors Rust n'est pas un tel langage. Il n'y a aucun moyen de définir un struct qui hérite des champs et des implémentations de méthodes du struct parent sans utiliser une macro.

Cependant, si vous êtes habitué à avoir l'héritage dans votre outilbox de programmation, vous pouvez utiliser d'autres solutions en Rust, selon votre raison pour laquelle vous avez opté pour l'héritage en premier lieu.

Vous choisiriez l'héritage pour deux raisons principales. La première est la réutilisation du code : vous pouvez implémenter un comportement particulier pour un type, et l'héritage vous permet de réutiliser cette implémentation pour un autre type. Vous pouvez le faire d'une manière limitée dans le code Rust en utilisant les implémentations de méthodes par défaut de traits, que vous avez vue dans le Listing 10-14 lorsque nous avons ajouté une implémentation par défaut de la méthode summarize sur le trait Summary. Tout type implémentant le trait Summary aurait la méthode summarize disponible sans aucun code supplémentaire. Cela est similaire à une classe mère ayant une implémentation d'une méthode et une classe enfant héritière ayant également l'implémentation de la méthode. Nous pouvons également remplacer l'implémentation par défaut de la méthode summarize lorsque nous implémentons le trait Summary, ce qui est similaire à une classe enfant qui remplace l'implémentation d'une méthode héritée d'une classe mère.

L'autre raison d'utiliser l'héritage est liée au système de types : pour permettre à un type enfant d'être utilisé dans les mêmes emplacements que le type parent. Cela s'appelle également polymorphisme, ce qui signifie que vous pouvez substituer plusieurs objets les uns aux autres à l'exécution s'ils partagent certaines caractéristiques.

Polymorphisme

Pour de nombreuses personnes, le polymorphisme est synonyme d'héritage. Mais en fait, c'est un concept plus général qui désigne le code qui peut fonctionner avec des données de plusieurs types. Pour l'héritage, ces types sont généralement des sous-classes.

Rust utilise plutôt les génériques pour abstraire sur différents types possibles et les contraintes de traits pour imposer des contraintes sur ce que ces types doivent fournir. Cela s'appelle parfois polymorphisme paramétrique borné.

L'héritage est tombé en disgrâce récemment en tant que solution de conception de programmation dans de nombreux langages de programmation car il est souvent exposé au risque de partager plus de code que nécessaire. Les sous-classes ne devraient pas toujours partager toutes les caractéristiques de leur classe parent, mais le feront avec l'héritage. Cela peut rendre la conception d'un programme moins flexible. Il introduit également la possibilité d'appeler des méthodes sur des sous-classes qui n'ont pas de sens ou qui entraînent des erreurs car les méthodes ne s'appliquent pas à la sous-classe. En outre, certains langages ne permettent que l'héritage unique (ce qui signifie qu'une sous-classe ne peut hériter que d'une seule classe), limitant encore plus la flexibilité de la conception d'un programme.

Pour ces raisons, Rust adopte une approche différente en utilisant des objets de trait au lieu de l'héritage. Regardons comment les objets de trait permettent le polymorphisme en Rust.

Sommaire

Félicitations ! Vous avez terminé le laboratoire sur les Caractéristiques des langages orientés objet. Vous pouvez pratiquer d'autres laboratoires sur LabEx pour améliorer vos compétences.