Introduction à la Programmation Orientée Objet

Introduction à la Programmation Orientée Objet

Classes, Constructeurs et Objets dans Dart

Bonjour les amis et bienvenu dans ce nouveau tutoriel, consacré à une introduction à la programmation Orientée Objet (POO). Bien avant d'aller dans les détails des objets, classes et constructeurs, je vais commencer par me poser un certains nombre de question.

  • C'est quoi la POO?
  • Pourquoi l'utilisée?
  • Et en quoi pourrait elle bien servir?

La POO correspond à un paradigme, un concept. On entend par paradigme en Programmation, une manière d'aborder ou d'approcher la programmation informatique. C'est donc une façon presque universelle de résoudre les différents problèmes de programmation.

Comprendre les concepts de la POO peut être un avantage considérable, je dirais même un prérequis pour tous développeurs souhaitant écrire des programmes de grande tailles, complexes et maintenables, que ce soit pour le desktop, le web ou encore le mobile.
Aussi, est il à noter que la plupart des langages créés au cours de ses dernières années sont orientés objet(Python, C++, Java, JavaScript, Dart, PHP, Swift, ...).

La POO va nous servir à créer des programmes modulaires, à écrire du code réutilisable de projet en projet, à rendre le code facilement maintenable et évolutif. Plus concrètement, la POO va nous aider à proposer des logiciels ou applications plus robuste.
Toutefois, ce n'est pas forcement par ce qu'on va écrire du code objet que cela voudrait dire que tous nos logiciels seront de qualités ou que ce n'est que le code objet qui peut produire uniquement des logiciels de qualités.
Ainsi, pour produire des logiciels de qualité en Orientée 0bjet, déjà il faudrait qu'on comprenne bien les notions sous-jacentes et savoir comment l'appliquer dans un projet réel.

Dans l'expression POO, remarquer déjà qu'on a le mot Objet, C'est donc simple de dire que la programmation orientée objet est une programmation qui se base sur les objets. C'est à dire, pour construire des logiciels ou applications, les concepteurs et développeurs vont organiser leur code autours des objets. Mais déjà qu'est ce qu'un Objet?

1. Objet

Un objet est un champ de donnée possédant des attributs ou propriétés et un comportement unique. Par exemple, une facture, un utilisateur, un étudiant, une personne, un Bouton, une balise HTML peut être considéré comme un objet. Un objet personne peut être caractérisé par son nom, sa ville de résidence, son année de naissance. Un objet personne peut avoir des comportements ou effectuer des opérations tel que parler, danser, Jouer d'un instrument...
Ses caractéristiques de personne, dans le paradigme objet, sont appelées attributs ou propriétés et les opérations sont appelées méthodes.
En POO, les choses deviennent plus intéressant quand les objets communiquent entre eux.
Ainsi, un objet encapsule les données et de la logique. Ses données et logique doivent être regroupées au sein d'une même entité appelée classe(class en anglais et mot clef utilisé dans la plupart des langage de POO).

2. Classe

Une classe permet de regrouper donc les propriétés (caractéristiques ) d'un objet et les opérations(méthodes) qui peuvent être effectuées sur cet objet.
C'est un modèle définissant la structure commune de tous les objets qui en découleront. Plus simplement, une classe va nous permettre de créer autant d'objet qu'on veut.
En POO, une classe est identifiée par un nom commençant généralement par une lettre Majuscule.
Partant de tout ce qui à été dit, nous pouvons fournir une représentation syntaxique d'une classe en Dart.

 class Personne {

   /// proprieté1;
   /// proprieté2;
   /// ...
   /// ...
   /// proprietén;

   /// opération1;
   /// opération2;
   /// ...
   /// ...
   /// opérationn;
}

Dans la syntaxe suivante de la classe, les propriétés(proprieté1, proprieté2; proprietén) représentent simplement les variables de classe et surtout n'oublier pas qu'en Dart les variables sont souvent typées en général. Quant aux opérations (opération1, opération1, opérationn), elles représentent des fonctions ou procédures de la classe, elles aussi peuvent être typées.
Une première implémentation de la classe Personne est la suivante:

class Personne {
  String nom = 'Bertille';
  String villeDeResidence = 'Cotonou';
  int anneeDeNaissance = 2001;

  void parler() {
    print('Je suis une personne parlant Français');
  }

  void jouerInstrument() {
    print('Je suis une personne Jouant au Piano');
  }

  String danser() {
    return 'Je danse la salsa';
  }
}

Si vous vous rappelez bien, nous avons dis plus haut qu'une classe devrait nous servir de modèle pour créer autant d'objets qu'on le souhaite.
A partir de là, créons respectivement deux objets nommés personne1 et personnes2 et affichons les à l'aide de print depuis notre méthode main. Nous partons du principe que la classe Personne et la méthode mainsont dans le même fichier.
La méthode main représente le point d'entrer d'une application Dart pour ceux qui l'avaient déjà oublié!

void main(List<String> args) {
  Personne personne1 = new Personne();
  Personne personne2 =  Personne();
  print(personne1); // Instance of 'Personne'
  print(personne1); // Instance of 'Personne'

}

En affichant ses deux objets à l'aide de print, nous remarquons que la console nous affiche respectivement Instance of 'Personne', ce qui veut dire que nous avons ici une instance de Personne malgré le fait que nous avons créé les deux objets de manières différentes:

  • Pour l'objet personne1, nous utilisons le mot clef new devant la classe suivie de deux parenthèses, une ouvrante et l'autre fermante, suivie bien sur du point virgule(;) qui marque la fin d'une instruction en Dart.
    Le mot clef new utilisé ici et aussi utilisé dans plusieurs langages orientée objet, signifie que nous disons à Dart que nous voulons créer un objet, ou créer une instance , ou encore instancier la classe de type Personne.

  • Pour l'objet personne2, nous l'avons créé sans utilisé le mot clef new. Ce qui nous montre clairement que lors de la création d'objet en Dart l'utilisation du mot clef new est facultatif, à partir de là, nous instancierons dans la suite de ce tutoriel les classes sans utiliser le mot clef new.

Maintenant que nous savons instancier les classes, c'est bien beau, mais c'est vraiment insuffisant pour faire un programme. Essayons de monter un peu en compétences en manipulant la classe.
Déjà, j'aimerais que vous sachiez que chaque objet créer peut accéder à tout ce qui est dans la classe. Si vous me suivez bien, actuellement dans la classe Personne nous avons les attributs (nom, villeDeResidence, anneeDeNaissance) et les méthodes(parler, jouerInstrument, danser), donc les objets personne1 et personne2 ont accès.
Pour accéder à un attribut ou une méthode depuis l'objet, il faut simplement faire l'objet point(.) la méthode ou l'attribut. Le code suivant je crois sera plus parlant que ma phrase précédente

void main(List<String> args) {
  Personne personne1 = new Personne();
  Personne personne2 = Personne();
  print(personne1); // Instance of 'Personne'
  print(personne1); // Instance of 'Personne'

  /// personne1 Accède aux attributs de Personne
  print(personne1.nom); // Bertille
  print(personne1.villeDeResidence); // Cotonou
  print(personne1.anneeDeNaissance); // 2001

  /// personne2 Accède aux attributs de Personne
  print(personne2.nom); // Bertille
  print(personne2.villeDeResidence); // Cotonou
  print(personne2.anneeDeNaissance); // 2001

  /// personne1 Accède aux méthodes de Personne
  personne1.parler(); // Je suis une personne parlant Français'
  personne1.jouerInstrument(); //Je suis une personne Jouant au Piano
  print(personne1.danser()); // Je danse la salsa

  /// personne2 Accède aux méthodes de Personne
  personne2.parler(); // Je suis une personne parlant Français'
  personne2.jouerInstrument(); //Je suis une personne Jouant au Piano
  print(personne2.danser()); // Je danse la salsa
}

Avec les deux objets(personne1 et personne2), nous avons utilisé tous les attributs et toutes les méthodes de la classe Personne. Mais je ne sais pas si vous aviez remarqué quelques chose d'assez étrange, la console nous renvoie deux objets identique. Pour le moment tous les objets créés ont comme nom Bertille, comme ville de résidence Cotonou et comme année de naissance 2001. Je ne sais pas pour vous, mais pour moi je ne vois vraiment pas d’intérêt que cette classe personne nous fournisse uniquement des objets identiques.
Ne serait il pas intéressant de changer ce comportement actuel? Si cela t’intéresse vraiment, je crois tu devrais lire la suite du tutoriel.

3. Constructeur

Pour qu'une classe puisse être instancié, il faudrait qu'elle admettre au moins un constructeur.
Un constructeur est donc une méthode spéciale d'une classe qui nous permet de créer des objets. C'est donc une méthode qui est toujours appelée dès qu'un objet est créé.
Mais 🤔🤔 attends là, n'avons nous pas créer des objets dans les exemples plus haut, sans pourtant avoir de constructeur dans notre classe ???
Bien sur nous avons créé des objets 😁😁😁😁, vous voyez les amis, quand les croyants des religions monothéiste (Christianisme, Islam, Judaïsme) disent que chaque Homme nait pécheur, ils expliquent cela en disant que nous somme des descendants de Adam et Eve, de manière analogique, les programmeurs de Dart disent que chaque objet qu'on créé en utilisant Dart descendra toujours d'une classe Object.
Sympa n'est-ce pas les gars et les go ?
De la manière dont les Hommes ont hérités des péchés de Adam et Eve, c'est de la même manière les objets créés en Dart héritent toujours du Constructeur de l’ancêtre Object.
Si vous m'aviez bien suivi, j’essaie simplement de vous dire que lorsque vous écrivez une classe, elle admet un constructeur par défaut, appelé implicitement depuis le parent(ancêtre). Alors si nous ne définissons pas de constructeur, c'est celui du parent qui est appelée et donc utilisé. Il est tant que nous définissons le constructeur de notre classe.
En Dart, un constructeur porte le même nom que la classe et il nous permet aussi d'initialiser des variables. Alors, donnons nous comme objectif de créer des objets de types personne différents.
Modifions alors la classe Personne précédente en ajoutant un constructeur qui nous permettra de créer plusieurs objets différents.


class Personne {
  String nom;
  String villeDeResidence;
  int anneeDeNaissance;

  /// Constructeur avec initialisation des variables :
  /// nom, villeDeResidence et anneeDeNaissance
  Personne(this.nom, this.villeDeResidence, this.anneeDeNaissance);

  void parler() {
    print('Je suis une personne parlant Français');
  }

  void jouerInstrument() {
    print('Je suis une personne Jouant au Piano');
  }

  String danser() {
    return 'Je danse la salsa';
  }
}

Le mot clef this utilisé dans le constructeur fait référence à la classe actuelle (Personne) et remarquez dans ce cas que le constructeur n'a même pas de corps, c'est à dire non délimité par l'accolade ouvrante et fermante {} comme dans le cas des méthodes.

Créons à nouveau deux personnes (personne3 et personne4) en utilisant ce nouveau constructeur et affichons leur nom respectivement.

void main(List<String> args) {
  Personne personne3 = Personne('Mafo', 'Libreville', 1987);
  Personne personne4 = Personne('John', 'Moanda', 1982);

  print(personne3.nom); // Mafo
  print(personne4.nom); // John
}

En observant le retour de la console, nous voyons bien que personne3 et personne4 sont différentes, nous venons donc t'atteindre l'objectif que nous nous étions fixés, celui de créer des objets différents.

Imaginons un instant, que la classe Personne grandisse et que nous ayons plus de dix voir même plus de vingt variables à initialiser dans le constructeur, lors de l'appelle du constructeur, au moment où nous lui passons les valeurs, comment savoir exactement que les valeurs passées en paramètres correspondent à telle ou telle variable de la classe ?
Dans un tutoriel précedent sur les fonctions en Dart , nous avons introduit la notion d'arguments nommés. Il est tant à présent de réutiliser cette notion afin d'avoir un constructeur plus facile à appeler par la suite.

class Personne {
  String nom;
  String villeDeResidence;
  int anneeDeNaissance;

  /// Constructeur avec initialisation des variables :
  /// nom, villeDeResidence et anneeDeNaissance en
  /// utilisant des arguments nommés
  Personne(
      {required this.nom,
      required this.villeDeResidence,
      required this.anneeDeNaissance});

  void parler() {
    print('Je suis une personne parlant Français');
  }

  void jouerInstrument() {
    print('Je suis une personne Jouant au Piano');
  }

  String danser() {
    return 'Je danse la salsa';
  }
}

Les accolades {} à l’intérieur du constructeur montre simplement que les arguments passés dans le constructeur doivent être explicitement mentionnés lors de son appelle. Le mot clef required signifie que cette variable est obligatoire, elle doit donc forcement être renseignée lors de l'appelle du constructeur.

Le code suivant nous montre comment cela ce fait.

void main(List<String> args) {
  Personne personne7 = Personne(
      nom: 'Pierre', villeDeResidence: 'Bohicon', anneeDeNaissance: 2013);

  Personne personne8 = Personne(
      nom: 'Amandine', villeDeResidence: 'Moanda', anneeDeNaissance: 1990);

  print(personne7.nom); // Pierre
  print(personne8.nom); // Amandine
}

Le constructeur en Dart peut aussi ressembler à celui d'autres langages de POO, en voici un exemple

class Personne {
  String? nom;
  String? villeDeResidence;
  int? anneeDeNaissance;

  /// Constructeur classique avec initialisation des variables 
  Personne(String n, String v, int a) {
    nom = n;
    villeDeResidence = v;
    anneeDeNaissance = a;
  }

  void parler() {
    print('Je suis une personne parlant Français');
  }

  void jouerInstrument() {
    print('Je suis une personne Jouant au Piano');
  }

  String danser() {
    return 'Je danse la salsa';
  }
}

Le point d'interrogation devant la déclaration de type de variable de classe indique qu'une variable peut avoir la valeur null. En effet, dans Dart les variables de classes doivent être initialisées. Si dans l'exemple plus haut, nous n'ajoutons pas des points d'interrogations devant les déclarations de type, une erreur de compilation sera levée.
En Dart, une variable peut être initialisée plus tard, pour cela nous utiliserons le mot clef late, ainsi les variables de classes précédentes deviennent :

  late String nom;
  late String villeDeResidence;
  late int anneeDeNaissance;

Dans d'autres langages orientés objets comme le C++, il est possible de surcharger votre constructeur. Cela vous permet de créer un objet de plusieurs façons différentes. Cela signifie que vous pouvez avoir différents constructeurs portant le même nom, mais avec une signature différente ou un ensemble d'arguments différent. Mais en Dart, nous ne pouvons pas avoir plusieurs constructeurs portant le même nom. Dart a ainsi trouvé un moyen pour contourner cela en introduisant les constructeurs nommés.

L'exemple suivant présente un constructeur nommé.

  Personne.withNationality(
      this.nom, this.villeDeResidence, this.anneeDeNaissance, this.nationalite);

Ainsi, nous affichons le code complet de la classe, ainsi que la méthode main afin que vous puissiez avoir une vision globale.

void main(List<String> args) {
  // Appelle du constructeur nommé
  Personne personneAutre =
      Personne.withNationality('DOKOUA', 'Lambarene', 1982, 'Gabonaise');

  Personne personne = Personne('KOKOU', 'Lome', 1952);
  // donnons à l'objet personne la nationalité Togolaise
  personne.nationalite = 'Togolaise';

  print(personneAutre); // Instance of 'Personne'
  print(personne.nationalite); // Togolaise
}

class Personne {
  late String nom;
  late String villeDeResidence;
  late int anneeDeNaissance;

  /// Nouvelle propriété initialisée dans le constructeur nommé
  late String nationalite;

  /// Constructeur avec initialisation des variables :
  Personne(String n, String v, int a) {
    nom = n;
    villeDeResidence = v;
    anneeDeNaissance = a;
  }

  Personne.withNationality(
      this.nom, this.villeDeResidence, this.anneeDeNaissance, this.nationalite);

  void parler() {
    print('Je suis une personne parlant Français');
  }

  void jouerInstrument() {
    print('Je suis une personne Jouant au Piano');
  }

  String danser() {
    return 'Je danse la salsa';
  }
}

Maintenant que vous saviez qu'est ce qu'un classe, un objet et un constructeur, en attendant un nouveau tutoriel, je vous invite à explorer la documentation de Dart et n'oubliez pas de laisser vos questions en commentaire si vous en avez, je vous répondrais!

Ok, les amis, à la prochaine !