Introduction à la programmation orientée objet dans Dart
Introduction à la programmation orientée objet dans Dart
-
Objectifs
-
Notion de POO en Dart
- La programmation orientée objet, ou POO, est un paradigme de programmation qui permet de structurer les programmes de manière à ce que les propriétés et les comportements soient regroupés dans des objets à part.
- Par exemple, un objet peut représenter une personne avec un nom, un âge, une adresse, etc., avec des comportements tels que marcher, parler, respirer et courir.
- En d’autres termes, la programmation orientée objet est une approche permettant de modéliser des éléments concrets du monde réel tels que les voitures les personnes…, ainsi que des relations entre des entités telles que les entreprises et les employés, les étudiants et les enseignants, etc.
- La modélisation POO modélise des entités réelles sous la forme d’objets logiciels certaines données qui leur sont associées et peuvent remplir certaines fonctions.
-
Présentation
- La programmation orientée objet (POO) est une méthode de structuration d’un programme en regroupant des propriétés et des comportements associés dans des objets individuels.
- Dart est un langage de programmation orienté objet et prend en charge tous les concepts de la programmation orientée objet tels que les classes, l’objet, l’héritage, le polymorphisme, les interfaces et les classes abstraites. .
-
Classes et Objets en Dart
- En Dart, tout est un objet. Les objets sont des instances de classes.
- Une classe est un modèle ou un plan pour créer des objets. Elle définit les propriétés (attributs) et les méthodes (fonctions) associées aux objets.
- Dans cet exemple, Person est une classe avec deux propriétés (name et age) et une méthode (printInfo).
- Les classes Dart sont définies comme le plan des objets associés. Une classe est un type de données défini par l’utilisateur qui décrit ses caractéristiques et son comportement. Pour obtenir toutes les propriétés de la classe, nous devons créer un objet de cette classe.
- Dart est un langage orienté objet, il intègre donc la notion de classe. La syntaxe est similaire à celle de Java mais Dart propose quelques fonctionnalités en plus comme les constructeurs nommés ou encore la surcharge d’opérateur. Toutes les classes héritent de la classe Object.
- Une classe a des propriétés et des méthodes.
- propriété signifie, par exemple, qu’une voiture aura des couleurs, Combien y aura-t-il de places ?
- La méthode est de savoir comment conduire la voiture. Si vous tournez, comment devriez-vous tourner ? Des méthodes telles que comment freiner.
-
Syntaxe d’une classe Dart
- La syntaxe de la classe est donnée ci-dessous.
-
Explications :
- Champs (propriétés): Ce sont les variables membres de la classe qui représentent les données de l’objet.
- Constructeur: Initialise les objets de la classe. Dart supporte plusieurs formes de constructeurs.
- Vous pouvez également utiliser une syntaxe plus concise pour initialiser les champs directement dans la liste des paramètres du constructeur :
- Méthodes: Ce sont les fonctions membres de la classe.
- Cette syntaxe de base permet de créer des classes en Dart avec des champs, des constructeurs et des méthodes. Les classes peuvent également inclure d’autres fonctionnalités telles que l’héritage, l’encapsulation, et le polymorphisme pour construire des structures de programme plus complexes et modulaires.
-
Encapsulation
- L’encapsulation est l’un des quatre principes fondamentaux de la programmation orientée objet (POO), aux côtés de l’héritage, du polymorphisme et de l’abstraction.
- En Dart, l’encapsulation est la pratique de regrouper les données (les champs ou propriétés) et les méthodes qui les manipulent au sein d’une même unité, généralement une classe.
- L’objectif principal de l’encapsulation est de cacher l’implémentation interne de la classe et de fournir un accès contrôlé aux données.
- Voici quelques aspects clés de l’encapsulation en Dart :
-
Champs Privés :
- En Dart, l’encapsulation est souvent mise en œuvre en déclarant les champs comme privés en les préfixant d’un underscore
_
. - Ces champs privés ne sont pas accessibles directement depuis l’extérieur de la classe.
-
Accès Contrôlé :
- Pour permettre l’accès aux données encapsulées, des méthodes publiques (getters et setters) sont souvent définies.
- Ces méthodes permettent un accès contrôlé aux données internes de la classe, permettant la validation ou la modification des valeurs avant ou après leur accès.
-
Protection contre la Modification Directe :
- L’encapsulation protège contre la modification directe des champs privés en dehors de la classe, réduisant ainsi le risque d’erreurs et facilitant la maintenance du code.
-
Modification de l’Implémentation sans Impact Extérieur :
- En encapsulant les détails internes de la classe, vous pouvez changer l’implémentation sans affecter le reste du programme. Tant que les méthodes publiques restent inchangées, le code client n’a pas besoin d’être modifié.
-
Ce familiariser avec les concepts fondamentaux de la programmation orientée objet (POO) tout en utilisant le langage de programmation Dart. .
class Person {
String name;
int age;
// Constructeur
Person(this.name, this.age);
void printInfo() {
print('Nom: $name, Âge: $age');
}
}
class MaClasse {
// Champs (propriétés)
TypeDeDonnée nomDuChamp1;
TypeDeDonnée nomDuChamp2;
// ...
// Constructeur
MaClasse(param1, param2, /* ... */) {
// Initialisation des champs
this.nomDuChamp1 = param1;
this.nomDuChamp2 = param2;
// ...
}
// Méthodes
TypeDeRetour nomDeLaMethode1(TypeDeParametre1 param1, /* ... */) {
// Corps de la méthode
// ...
}
TypeDeRetour nomDeLaMethode2(TypeDeParametre2 param2, /* ... */) {
// Corps de la méthode
// ...
}
// ...
}
class MaClasse { ... }: Définit une nouvelle classe nommée MaClasse.
TypeDeDonnée nomDuChamp1;
TypeDeDonnée nomDuChamp2;
// ...
MaClasse(param1, param2, /* ... */) {
this.nomDuChamp1 = param1;
this.nomDuChamp2 = param2;
// ...
}
MaClasse(this.nomDuChamp1, this.nomDuChamp2, /* ... */) {
// Aucune initialisation nécessaire ici
}
TypeDeRetour nomDeLaMethode1(TypeDeParametre1 param1, /* ... */) {
// Corps de la méthode
// ...
}
TypeDeRetour nomDeLaMethode2(TypeDeParametre2 param2, /* ... */) {
// Corps de la méthode
// ...
}
class Person {
String _name; // Champ privé
int _age; // Champ privé
Person(this._name, this._age);
}
class Person {
String _name; // Champ privé
int _age; // Champ privé
Person(this._name, this._age);
// Getter
String get name => _name;
// Setter
set age(int value) {
if (value >= 0) {
_age = value;
} else {
print('L\'âge ne peut pas être négatif.');
}
}
}
En résumé, l’encapsulation en Dart favorise la modularité, la réutilisation du code et la réduction des erreurs en restreignant l’accès direct aux détails internes de la classe. Elle encourage la définition d’interfaces publiques bien définies pour interagir avec les objets plutôt que de manipuler directement leurs états internes.
Héritage
- L’héritage est l’un des principes fondamentaux de la programmation orientée objet (POO) qui permet à une classe (appelée classe dérivée ou sous-classe) de hériter des propriétés et des comportements d’une autre classe (appelée classe de base ou superclasse). En Dart, l’héritage est pris en charge, et voici les principaux concepts à connaître :
-
Déclaration d’une Classe avec Héritage :
- En Dart, pour déclarer une classe qui hérite d’une autre, on utilise le mot-clé extends.
- Dans cet exemple, la classe Employee hérite de la classe Person. La classe Employee a donc accès aux propriétés et méthodes de la classe Person, en plus de ses propres membres.
class Person {
String name;
int age;
Person(this.name, this.age);
void printInfo() {
print('Nom: $name, Âge: $age');
}
}
class Employee extends Person {
String department;
Employee(String name, int age, this.department) : super(name, age);
void printEmployeeInfo() {
super.printInfo(); // Appel à la méthode de la classe de base
print('Département: $department');
}
}
Appel à la Classe de Base avec super :
- Le mot-clé super est utilisé pour appeler les membres (méthodes ou propriétés) de la classe de base à partir de la classe dérivée.
void printEmployeeInfo() {
super.printInfo(); // Appel à la méthode printInfo() de la classe de base
print('Département: $department');
}
Surcharge de Méthodes (Override) :
- Une classe dérivée peut redéfinir (ou surcharger) une méthode de la classe de base en utilisant le mot-clé @override.
@override
void printInfo() {
print('Employé - Nom: $name, Âge: $age');
}
Constructeurs dans les Classes Dérivées :
- Les classes dérivées peuvent appeler le constructeur de la classe de base en utilisant super dans leur propre constructeur.
Employee(String name, int age, this.department) : super(name, age);
Utilisation de l’Héritage pour la Réutilisation du Code :
- L’héritage permet de réutiliser le code en évitant la duplication. Les membres communs à plusieurs classes peuvent être placés dans une classe de base, tandis que les classes dérivées peuvent se concentrer sur leurs spécificités.
void main() {
Employee employee = Employee('Alice', 30, 'Informatique');
employee.printEmployeeInfo();
}
Polymorphisme
- En Dart, le polymorphisme se réfère à la capacité d’un objet à prendre différentes formes, généralement en utilisant l’héritage et les interfaces.
- Il existe deux types principaux de polymorphisme en Dart : le polymorphisme de sous-typage (ou polymorphisme d’héritage) et le polymorphisme d’interface.
-
Polymorphisme de sous-typage (Polymorphisme d’héritage):
- Le polymorphisme de sous-typage est basé sur l’héritage. Dart prend en charge l’héritage simple, et les classes peuvent hériter d’une seule classe à la fois.
- Voici un exemple simple de polymorphisme de sous-typage en Dart :
- Dans cet exemple, la classe Animal est la classe de base, et les classes Chien et Chat héritent de la classe Animal. Les méthodes spécifiques de chaque classe dérivée (chien et chat) peuvent être appelées via une référence de type Animal.
class Animal {
void faireDuBruit() {
print('Certains animaux font du bruit.');
}
}
class Chien extends Animal {
@override
void faireDuBruit() {
print('Le chien aboie.');
}
}
class Chat extends Animal {
@override
void faireDuBruit() {
print('Le chat miaule.');
}
}
void main() {
Animal animal = Chien();
animal.faireDuBruit(); // Le chien aboie.
animal = Chat();
animal.faireDuBruit(); // Le chat miaule.
}
Polymorphisme d’interface:
- Dart prend également en charge le polymorphisme d’interface. Les interfaces définissent un contrat que les classes peuvent implémenter. Voici un exemple d’interface en Dart :
abstract class Animal {
void faireDuBruit();
}
class Chien implements Animal {
@override
void faireDuBruit() {
print('Le chien aboie.');
}
}
class Chat implements Animal {
@override
void faireDuBruit() {
print('Le chat miaule.');
}
}
void main() {
Animal animal = Chien();
animal.faireDuBruit(); // Le chien aboie.
animal = Chat();
animal.faireDuBruit(); // Le chat miaule.
}
Le polymorphisme en Dart offre une flexibilité qui facilite la création de code réutilisable et extensible. En choisissant le type d’héritage ou d’interface approprié, les développeurs peuvent concevoir des systèmes logiciels plus modulaires et évolutifs.
Activité
- Compréhension des Classes :
- Quelle est la classe de base dans cet exemple de polymorphisme en Dart?
- Quelles sont les classes dérivées? Quelles méthodes chaque classe dérivée override-t-elle?
- Comment la méthode faireDuBruit() de la classe de base est-elle implémentée?
- Appel de Méthodes :
- Comment appelez-vous la méthode faireDuBruit() pour un objet de type Chien?
- Quelle méthode est appelée lorsque vous appelez ecouterAnimal(animal1) dans la fonction main()?
- Si une nouvelle classe dérivée Oiseau était ajoutée, comment devriez-vous l’utiliser avec la fonction ecouterAnimal()?
- Création d’Objets :
- Créez une nouvelle classe dérivée appelée Oiseau qui étend la classe Animal. Ajoutez une méthode faireDuBruit() qui imprime « Le chant de l’oiseau ».
- Créez une instance de cette nouvelle classe et appelez la fonction ecouterAnimal() avec cette instance dans la fonction main().
- Concepts de Polymorphisme :
- Expliquez en vos propres mots ce qu’est le polymorphisme.
- En quoi le polymorphisme rend-il le code plus flexible et réutilisable?
- Utilisation de Types Abstraits :
- Ajoutez une méthode abstraite manger() à la classe de base Animal. Implémentez cette méthode dans chacune des classes dérivées.
// Classe de base
class Animal {
void faireDuBruit() {
print('Le son de l\'animal');
}
}
// Classes dérivées
class Chien extends Animal {
@override
void faireDuBruit() {
print('Le chien aboie');
}
}
class Chat extends Animal {
@override
void faireDuBruit() {
print('Le chat miaule');
}
}
// Fonction utilisant le polymorphisme
void ecouterAnimal(Animal animal) {
animal.faireDuBruit();
}
void main() {
// Utilisation du polymorphisme
Animal animal1 = Chien();
Animal animal2 = Chat();
ecouterAnimal(animal1); // Le chien aboie
ecouterAnimal(animal2); // Le chat miaule
}
Solution
- Nouvelle Classe Dérivée Oiseau :
class Oiseau extends Animal {
@override
void faireDuBruit() {
print('Le chant de l\'oiseau');
}
}
void main() {
// ...
Oiseau oiseau = new Oiseau();
ecouterAnimal(oiseau); // Le chant de l'oiseau
}
- Le polymorphisme est le concept selon lequel un objet peut être traité comme une instance de sa classe de base, même s’il est réellement une instance d’une classe dérivée. Cela permet d’écrire du code qui peut fonctionner de manière générique avec des objets de différentes classes, à condition qu’ils partagent une hiérarchie commune. En termes simples, le polymorphisme permet de traiter différents types d’objets de manière uniforme.
- Flexibilité : Permet de traiter des objets de manière générique, facilitant l’extension du code avec de nouvelles classes dérivées sans modifier le code existant.
- Réutilisabilité : Le code peut être réutilisé pour différents types d’objets, ce qui réduit la duplication de code et améliore la maintenabilité.
- Ajout d’une Méthode Abstraite manger() à la Classe de Base Animal :
abstract class Animal {
void faireDuBruit() {
print('Le son de l\'animal');
}
// Méthode abstraite
void manger();
}
class Chien extends Animal {
@override
void faireDuBruit() {
print('Le chien aboie');
}
@override
void manger() {
print('Le chien mange de la viande');
}
}
class Chat extends Animal {
@override
void faireDuBruit() {
print('Le chat miaule');
}
@override
void manger() {
print('Le chat mange du poisson');
}
}
class Oiseau extends Animal {
@override
void faireDuBruit() {
print('Le chant de l\'oiseau');
}
@override
void manger() {
print('L\'oiseau mange des graines');
}
}
Applications
-
App01
-
Enoncé
- Rédigez une classe Bâtiment comprenant les attributs suivants :
- La classe Bâtiment doit être équipée des constructeurs suivants :
- Assurez-vous que la classe Bâtiment inclut des accesseurs et mutateurs pour ses différents attributs. En outre, la classe Bâtiment devrait contenir une méthode ToString() qui génère une représentation textuelle du bâtiment.
- Élaborez une classe Maison qui hérite de la classe Bâtiment et qui possède les attributs suivants :
- NbPieces : le nombre de pièces de la maison.
- La classe Maison doit offrir les constructeurs suivants :
- De plus, assurez-vous que la classe Maison inclut des accesseurs et mutateurs (ou propriétés) pour ses divers attributs. Enfin, implémentez une méthode ToString() qui génère une représentation textuelle de la Maison.
- Enfin, élaborez un programme de test pour vérifier le fonctionnement correct des deux classes, Bâtiment et Maison.
-
Solution
adresse
-
Batiment()
Batiment(adresse)
-
Maison()
Maison(adresse, nbPieces)
class Batiment {
String adresse;
// Constructeurs
Batiment() : adresse = '';
Batiment.withAdresse(String adresse) : adresse = adresse;
// Accesseurs et mutateurs
String getAdresse() {
return adresse;
}
void setAdresse(String nouvelleAdresse) {
adresse = nouvelleAdresse;
}
// Méthode ToString
@override
String toString() {
return 'Bâtiment : Adresse - $adresse';
}
}
class Maison extends Batiment {
int nbPieces;
// Constructeurs
Maison() : nbPieces = 0;
Maison.withDetails(String adresse, int nbPieces)
: nbPieces = nbPieces,
super.withAdresse(adresse);
// Accesseurs et mutateurs
int getNbPieces() {
return nbPieces;
}
void setNbPieces(int nouveauNbPieces) {
nbPieces = nouveauNbPieces;
}
// Méthode ToString
@override
String toString() {
return 'Maison : Adresse - $adresse, Nombre de pièces - $nbPieces';
}
}
void main() {
// Test de la classe Bâtiment
Batiment batiment1 = Batiment();
print(batiment1); // Affiche : Bâtiment : Adresse -
Batiment batiment2 = Batiment.withAdresse('123 Rue Principale');
print(batiment2); // Affiche : Bâtiment : Adresse - 123 Rue Principale
// Test de la classe Maison
Maison maison1 = Maison();
print(maison1); // Affiche : Maison : Adresse - , Nombre de pièces - 0
Maison maison2 = Maison.withDetails('456 Rue Secondaire', 3);
print(maison2); // Affiche : Maison : Adresse - 456 Rue Secondaire, Nombre de pièces - 3
// Utilisation des accesseurs et mutateurs
maison1.setAdresse('789 Rue Tertiaire');
maison1.setNbPieces(5);
print(maison1); // Affiche : Maison : Adresse - 789 Rue Tertiaire, Nombre de pièces - 5
}