Dart est un langage orienté objet. Il prend en charge les fonctionnalités de programmation orientées objet (POO) telles que les classes, les interfaces, etc. Une classe en termes de POO est un modèle pour la création d’objets. Cette façon de programmer permet une meilleure structuration du code et augmente sa réutilisabilité ainsi que pleins d’autres avantages.
Salut, je suis ravis de te retrouver pour une nouvelle partie de ton apprentissage sur Flutter. Nous attaquons aujourd’hui la Programmation Orientée Objet en Dart, un des chapitres les plus importants pour bien programmer en Dart. Je t’invite à voir les bases de Dart, un autre chapitre aussi important sur quoi, ce chapitre s’appuie.
##Petit rappel - C'est quoi une classe ? Une classe est une entité logicielle qui représente, dans un système informatique, une entité existant dans la vraie vie (Ex: un chat, un client, etc.). Une classe est aussi un modèle ou une maquette à partir de quoi l'on pourra créer des objets. L'opération qui consiste à créer un objet à partir d'une classe s'appelle l'instanciation. Ainsi, on peut instancier plusieurs objets à partir d'une classe. - C'est quoi un objet ? Un objet est un élément concret créé à partir d'une classe. Cet objet est une occurence d'un ensemble d'entité de la vraie vie qu'une classe est chargée de matérialiser dans un système informatique (Ex: l'élève avec le matricule 001 est un objet de la classe "Eleve").
NB : nous nous efforçons d’utiliser des termes simples pour expliquer ce concept dans cet article. Si vous êtes intéressés par un cours d’initiation au concept d’orienté objet, nous vous donnons rendez-vous dans un prochain article qui traitera ce sujet plus en détails.
Les classes en Dart
La déclaration d’une classe en Dart se fait de la façon suivante :
class ClasseName {
//Les attributs
type var1;
type var2;
//Le constructeur
classeName(){}
//Les méthodes
void fonction1(){...}
int fonction1(){...}
//Le accesseurs
//Les getters
type get getVar1 { return var1; }
...
//Les setters
set setVar1(type var1) { this.var1 = var1; }
...
}
Une classe est donc constituée de trois types d’éléments. Les constructeurs, les attributs et les méthodes.
- Les attributs sont des variables qui peuvent être de tout type. Elles permettent de décrire une classe et sont utilisées pour stocker des informations dans la classe.
- Les méthodes sont des fonctions, comme vu dans l’article Débute avec le langage Dart. Elles sont utilisées pour effectuer des actions particulières sur les attributs déclarées dans la classe.
- Quant-aux constructeurs, ce sont des « méthodes particulières » qui permet de « construire » ou « fabriquer » un objet. C’est la première « méthode » qui s’exécute quand on instancie une classe.
- Les accesseurs qui sont des méthodes qui s’appliquent exclusivement sur les attributs de la classe, contrairement au méthodes qui peuvent être utilisées pour produit d’autre types d’actions.
Exemples de classe :
//Déclaration de la classe chat
class Chat{
String? nom;
String? couleur;
int? age;
Chat(String? nom, String? couleur, int? age){
this.nom = nom;
this.couleur = couleur;
this.age = age;
}
void mioler(){
print("$nom miaou");
}
void manger(String nourriture){
print("Miam, je mange $nourriture");
}
void marcher(){...}
}
Ainsi, le code ci-dessus nous montre comment déclarer. Nous traiterons les différentes parties de ce code en détail.
Notre classe Chat est composée :
- D’attributs (nom, couleur et âge) : qui sont des variables qui permettent de décrire comment un chat est représenté dans notre système. Ainsi, les chats qui seront instanciés à partir de notre classe auront tous un nom, une couleur et un age.
- De méthodes ou fonctions (mioler, manger et marcher) : qui permettent d’effectuer un traitement particulier sur notre objet. Les méthodes sont utilisées de façon générale pour décrire ou donner un comportement que peuvent avoir les objets d’une classe dans notre système. Dans notre cas, nos chats issus de notre classe peuvent tous mioler, manger et marcher par exemple.
- Un constructeur : le constructeur nous permet de former ou fabriquer un objet. C’est cette méthode particulière qui se chargera de donner une forme particulière aux objet issus de notre classe. Une classe en Dart peut avoir plusieurs constructeurs en son sein. Dans notre exemple, notre constructeur est
Chat(String? nom, String? couleur, int? age){
this.nom = nom;
this.couleur = couleur;
this.age = age;
}
Ainsi voici un exemple qui montre comment créer un objet en Dart :
//Création d'un objet chat à partir de la classe Chat
void main(){
Chat milou = Chat("Milou", "Gris", 5);
milou.manger("du lait"); //Sortie "Miam, je mange du lait"
milou.mioler(); //Sortie "Milou miaou"
}
Nous nous intéresserons plus en détails aux constructeurs dans la suite.
Les constructeurs
En Dart on peut déclarer un constructeur de deux façons.
- Le constructeur basique
//Exemple 1
classeName(){...}
//Exemple 2
classeName(type[?] var1, type[?] var2,...){...}
//[?] n'est pas obligatoire.
NB : utiliser le symbole ? marque que le paramètre peut contenir la valeur null. Si vous ne précisez pas le symbole dans la déclaration cela veut dire que le paramètre ne sera jamais null. Voir l’article sur le Null-safety en Dart pour plus de détails.
- Le constructeur nommé
//Exemple 1
classeName.foo(){...}
//Exemple 2
classeName.foo(type[?] var1, type[?] var2,...){...}
//[?] n'est pas obligatoire.
Ainsi, avec les constructeurs nommés en Dart, on peut déclarer plusieurs constructeurs dans une même classe qui remplisse différentes fonction au sein de la même class, contrairement aux constructeurs basiques.
Exemples :
//Déclaration de la classe chat
class Chat{
String? nom;
String? couleur;
int? age;
//Constructeur basique sans paramètres #1
Chat();
//Ou
//Constructeur basique avec paramètres #2
Chat(String? nom, String? couleur, int? age){
this.nom = nom;
this.couleur = couleur;
this.age = age;
}
//Constructeur nommé avec paramètres #3
Chat.nom(String? nom){
this.nom = nom;
}
//Constructeur nommé sans paramètres #4
Chat.getInstance(){}
}
La construction et l’appel d’un objet de cette classe sera fera comme suit :
//Création d'un objet chat à partir de la classe Chat
void main(){
//Utilisation du constructeur #1
Chat c1 = Chat();
//Utilisation du constructeur #2
Chat milou = Chat("Milou", "Gris", 5);
//Utilisation du constructeur #3
Chat c2 = Chat.nom("c2");
//Utilisation du constructeur #3
Chat c3 = Chat.getInstance();
}
NB : comme pour les fonctions, on peut aussi utiliser les différents types de paramètres pour déclarer nos paramètres de constructeurs. Je vous invite à voir la section Les fonctions en Dart de l’article Débute avec le langage Dart.
Encapsulation et notion de visibilité
L'encapsulation est un mécanisme consistant à rassembler les données et les méthodes au sein d'une structure en cachant l'implémentation de l'objet, c'est-à-dire en empêchant l'accès aux données par un autre moyen que les services proposés. source : DataScientest
Ce mécanisme est mis en oeuvre à travers la notion de visibilité. La notion de visibilité désigne la portée qu’a un « littéral » (variable, attribut ou méthode) dans un programme informatique. En d’autres termes, il s’agit de savoir jusqu’où une variable, un attribut ou une méthode peut être accessible (en lecture et en écriture). Ce principe, en POO, est utilisé pour mettre en place une certaine sécurité sur les attributs et méthodes d’une classe.
Il existe plusieurs type de visibilité en POO notamment la visibilité
- Public
- Private
- Package
- Protected
Le langage Dart ne met en oeuvre que deux types de visibilités : Public et Private.
class Chat {
String? nom; //Déclaration d'un attribut public
String? _couleur; //Déclaration d'un attribut private
void mioler(){} //Déclaration d'une méthode public
void _manger(String nourriture){...} //Déclaration d'une méthode private
}
Comme vous pouvez le voir. Par défaut, les attributs et méthodes déclarés sont public. Pour rendre un attribut ou une méthode privée, vous devez préfixer le nom du « littéral » d’un _ (UNDERSCORE).
Il est important de préciser que cette notion s’applique aussi bien aux attributs, aux méthodes qu’aux classes elles-même ! On peut donc rendre une classe privée de la sorte
class _Chat{...}
Une fois marqué privé, le littéral ne pourra être accessible que dans l’étendu de son conteneur (Ex : la classe ou le fichier). Pour y accéder en dehors, vous pouvez utiliser les accesseurs.
Les accesseurs (getters & setters)
En POO, les accesseurs désignent des méthodes qui permettent d’accéder aux attributs privés d’une classe en lecture et écriture. Il y en a de deux types, les getters et les setters.
- Les getters
Les getters sont des fonctions qui servent essentiellement à lire la valeur d’un attribut privé d’une classe. La déclaration en Dart se fait comme suit :
class Chat {
...
String? _couleur; //Déclaration d'un attribut private
//Déclaration d'un getter
String? get couleur => _couleur;
//Ou
String? get couleur { return _couleur; }
...
}
- Les setters
Les setteurs sont aussi des fonctions qui servent essentiellement à assigner une valeur aux attributs privés. La déclaration en Dart se fait comme suit :
class Chat {
...
String? _couleur; //Déclaration d'un attribut private
//Déclaration d'un setter
set couleur(String? couleur) => _couleur = couleur;
//Ou
set couleur(String? couleur) { _couleur = couleur; }
...
}
- Utilisation ou appel d’un getter ou setter
class Chat {
...
String? _couleur; //Déclaration d'un attribut private
String? get couleur => _couleur;
set couleur(String? couleur) { _couleur = couleur; }
...
}
void main(){
Chat minou = Chat();
print(minou._couleur); //Erreur
print(minou.couleur); //Appel le getter
minou.couleur = "Gris"; //Appel le setter
print(minou.couleur);
//Sortie
Gris
}
L’héritage
L’héritage est le mécanisme par lequel on créé une classe B à partir d’une autre classe existante A. On dit alors que la classe B hérite de la classe A. Ainsi, la classe A est appelé « classe parente » ou « super classe » et la classe B est la « classe enfant » ou « sous classe ». L’héritage en Dart se fait en utilisant le mot clé extends.
//Classe parente
class Animal{...}
//Classe enfant
class Chat extends Animal{...}
NB : Dart ne supporte pas l’héritage multiple.
Ainsi, la classe Chat hérite de Animal. Chat hérite alors de tous les attributs et méthodes public de Animal.
class Animal{
String? nom;
void respirer(){ print("$nom respire."); }
}
//Classe enfant
class Chat extends Animal{}
void main(){
Chat minou = Chat();
minou.nom = "Minou";
minou.respirer();
//Sortie
Minou respire.
}
Notion de polymorphisme
Le nom de polymorphisme vient du grec et signifie qui peut prendre plusieurs formes. Cette caractéristique est un des concepts essentiels de la programmation orientée objet. Alors que l'héritage concerne les classes (et leur hiérarchie), le polymorphisme est relatif aux méthodes des objets. Source : CommentCaMarche.net
En terme clair, le polymorphisme est la faculté qu’une méthode a de s’exécuter différemment selon le contexte dans lequel elle est appelée. Exemple :
class FormeGeometrique {
double calculAir(){...}
}
class Carre extends FormeGeometrique{
double cote;
Carre(this.cote);
@override
double calculAir(){ return cote * cote; }
}
class Cercle extends FormeGeometrique{
double rayon;
Cercle(this.rayon);
@override
double calculAir(){ return 3.14 * rayon * rayon; }
}
void main(){
Carre c1 = Carre(4);
print(c1.calculAir()); //Sortie 16;
Cercle c2 = Cercle(2);
print(c2.calculAir()); //Sortie 12,56
}
Méthode et attributs statiques
Le mot-clé static s’applique sur les données membres d’une classe (attributs et méthodes). Un attribut statique conserve sa valeur jusqu’à la fin de l’exécution du programme. Les attributs et méthodes statiques sont référencés par le nom de la classe. Exemple :
class Constantes {
static int version = 12;
static void direBonjour(){
print("Bonjour");
}
}
void main(){
print(Constantes.version); //Sortie 12
Constantes.direBonjour(); //Sortie Bonjour
}
Super et this
Super et this sont deux mots clés utilisés dans le contexte des classes en POO.
- Super : est utilisé, à l’intérieur d’une classe, pour accéder aux attributs, méthodes et constructeur publiques de la classe parentes.
class Animal {
String nom;
Animal(this.nom);
void crier()=> print("Je crie");
}
class Chat extends Animal{
//Initialisation de la variable nom en appelant le constructeur de Animal
Chat(String nom) : super(nom);
@override
void crier(){
super.crier();
print("Je miole");
}
}
class Chien extends Animal{
//Initialisation de la variable nom en appelant le constructeur de Animal
Chien(String nom) : super(nom);
@override
void crier(){
super.crier();
print("J'aboie");
}
}
void main(){
Chat minou = Chat("Minou");
minou.crier();
//Sortie
Je crie
Je miole
Chien flash = Chien("flash");
flash.crier();
//Sortie
Je crie
J'aboie
}
- This : le mot clé this fait référence à l’instance actuelle de la classe.
class Chat {
String nom;
//Initialisation de la variable nom en appelant le constructeur de Animal
Chat(String nom){
this.nom = nom;
}
@override
void crier(){
super.crier();
print("Je miole");
}
}
Ici, le nom du paramètre nom et le nom du champ nom de la classe sont identiques. Par conséquent, pour éviter toute ambiguïté, le champ de la classe est préfixé par le mot-clé this. L’exemple suivant explique la même chose.
Ceci marque la fin de cet article. J’espère que vous avez trouvé plaisir le lire. Pour toutes questions, remarques ou suggestions vous pouvez me contacter ou laisser cela en commentaire. Allez, on se dit à très bientôt pour un nouvel article. Peace 😉!