Sommaire
Programmation Orientée Objet, Design/Architectural Patterns, … Vous en avez déjà entendu parler. Mais savez vous précisément ce en quoi ça consiste ?
Ces 8 articles vous permettront de comprendre les grands principes qui se cachent derrière ces termes techniques, mais également d’apprendre comment et pourquoi les utiliser.
- La Programmation Orientée Objet
- Introduction à UML : Diagrammes de classe
- Concepts de base de POO
- Les Patrons de Conception (Design Patterns)
- Les Patrons d’Architecture (Architectural Patterns)
- Concepts avancés de POO
- Qualité de Code
- Web Services
Qu’est-ce qu’un objet ?
Un objet en programmation n’est pas si différent de ce qu’on considère comme objet dans la vie de tous les jours. Si je vous dis “Donnez-moi un objet”, vous pourriez m’apporter un stylo, un écran de télévision, ou bien encore un hélicoptère apache.
Partons sur l’exemple du stylo :
- Si je vous dis “décrivez-moi ce qu’est un stylo”, vous me répondez “Un stylo est un objet, qui a de l’encre, et qui permet d’écrire”.
- Si je vous dis “décrivez-moi ce qu’est un stylo précisément”, vous me répondez “Un stylo est un objet, avec un niveau d’encre compris entre 0% et 100%, et qui a une fonctionnalité : écrire.
- Si je vous dis “décrivez-moi ce qu’est un stylo comme un développeur”, vous me répondez “Un stylo est un objet, qui a une variable de type “Integer” appelé “niveauEncre”, et qui a pour fonction “ecrire”.
Et voilà, vous avez compris ce qu’est un objet 🙂 Vous voulez peut-être une description plus formelle ?
En informatique, un objet est un conteneur symbolique et autonome qui contient des informations et des mécanismes concernant un sujet.
Wikipedia
Un objet en programmation est donc la représentation d’une structure de données ayant un comportement bien défini. Un objet peut donc avoir ses propres variables, mais aussi ses propres fonctions.
Qu’est-ce qu’une classe ?
Vous vous souvenez des objets ? Et bien les classes constituent ce qui nous permettra de créer des objets. Comment ?
- Si je vous dis “faites-moi un gâteau”, vous allez vous reporter à une recette
(au chocolat ? aux fruits ?). - Si je vous dis “fabriquez-moi une voiture”, vous allez vous reporter à un modèle
(peugeot 206 ? Clio ?). - Si je vous dis “fabriquez-moi un chien”, vous allez vous reporter à une race
(labrador ? berger allemand ?).
Vous avez compris, dans la vie réelle, pour fabriquer quelque chose, vous avez besoin de sa définition. Comment fabriquer un stylo si l’on ne sait pas ce que c’est.
La classe, c’est une définition, un modèle, une recette pour fabriquer quelque chose : nos objets !
Si je dispose d’une recette de gâteau aux framboises (la Classe), je pourrais en faire autant que je le souhaite (Les objets). Un objet est donc basé sur une classe. Tous les gâteaux que je vais créer seront des objets basés sur la classe Gateau. On dit dans ce cas qu’un gâteau (un objet), est une instance de la classe Gateau.
En général, ça ressemble à ça :
Sur cet exemple, nous avons défini une classe appelée “Gateau”, qui possède 2 variables (poids et diametre), et une fonction qui retourne le poids (getPoids). Les variables sont appelées “attributs” et les fonctions “méthodes” lorsqu’on parle de Programmation Orientée Objet.
Nous pouvons facilement créer des gâteaux maintenant :
Gateau gateau1 = new Gateau();
float poids = gateau1.getPoids();
Gateau gateau2 = new Gateau();
Ici, nous avons instancié 2 objets de type Gateau, et nous pouvons récupérer le poids du gateau grâce à la méthode getPoids() de mon objet gateau1.
On utilise en général le mot-clef “new” pour créer un nouvel objet, suivi du nom de la classe et de parenthèses (on verra plus tard qu’il est possible de passer des paramètres lors de l’instanciation d’un objet).
La visibilité
Vous avez du apercevoir quelques mots clefs qui n’ont pas encore été expliqués dans les exemples précédents : public, private, protected. On appel cela la visibilité d’un attribut ou d’une méthode. Ces mots-clefs permettent de définir les “endroits” dans le code qui pourront faire appel à une méthode ou à un attribut de notre classe.
public
On peut faire appel à l’attribut/méthode n’importe où dans l’application. Par exemple :
class Gateau
{
public float poids;
}
Gateau gateau = new Gateau();
float poids = gateau.poids; // Fonctionne
private
On ne peut faire appel à un attribut/méthode que dans la classe elle-même. Par exemple :
class Gateau
{
private float poids;
public float calories;
public float getPoids()
{
return poids; // Fonctionne
}
}
Gateau gateau = new Gateau();
float poids = gateau.poids; // Ne fonctionne pas, car l'attribut "poids" est privé
float poids2 = gateau.getPoids(); // Fonctionne, car la méthode "getPoids" est publique
float calories = gateau.calories; // Fonctionne, car l'attribut "calories" est public
protected
Tout dépend du langage :
- Pour certains (PHP par exemple), “protected” est similaire à “private”, excepté le fait que l’attribut/méthode est accessible aux sous-classes qui étendent/implémentent la classe/interface mère (cf. L’héritage et Les interfaces).
- Pour d’autres (Java par exemple), “protected” signifie que l’attribut/méthode est uniquement accessible au sein du package dans lequel la classe est définie.
Constructeur / Destructeur
Est-il possible de définir un traitement pour initialiser un objet quand il est créé ? La réponse est oui ! Nous pouvons définir un constructeur pour notre classe. Il s’agit d’une méthode, portant généralement le nom de la classe (Java, C++, …), ou un mot clef tel que “__construct” (PHP). Cette méthode sera automatiquement exécutée lorsqu’une instance de la classe sera créée (lorsqu’on fait un “new MaClasse()”. Le constructeur est généralement utilisé pour définir la valeur des attributs par défaut. Notez qu’il est possible de passer des paramètres au constructeur. Exemple :
public void maClasse(int val) { ... }
Il faudra donc passer ces paramètres lors de l’instanciation. Exemple :
MaClasse monObjet = new MaClasse(10);
Le destructeur, comme son nom l’indique, permet de définir un traitement à effectuer lorsqu’un objet est supprimé de la mémoire. On peut s’en servir par exemple pour forcer la fermeture d’une connexion à la base de données, ou pour tout autre type de flux. Une fois encore, la syntaxe dépend du langage (“__destruct()“ en PHP, “~MaClasse()” en C++, etc…).
L’héritage
Imaginez le cas suivant : On vous demande de créer un jeu-vidéo basé sur l’univers de Game of Thrones. Vous vous dites “Super, grâce à mes connaissances en Programmation Orientée Objet, je vais pouvoir créer une classe “Humain”, une classe “Dragon”, et une classe “Nain”. Et chacun disposera de points de vie”.
Mais vous êtes de bons développeurs, et quelque part en vous, vous souffrez de devoir dupliquer le code qui concerne les points de vie (un attribut pv dans la classe Humain, un attribut pv dans la classe Dragon, etc…).
Rassurez-vous, l’héritage est là pour nous sauver !
Avec l’héritage, on va pouvoir créer une classe EtreVivant qui aura un attribut “pv”. Et nos classes “Humain”, “Dragon”, et “Nain”, pourront hériter de la classe “EtreVivant” afin de profiter du système de pv mis en place.
C’est la même chose si on veut créer une classe “Voiture” et une classe “Moto”. Les deux pourraient hériter d’attributs/méthodes d’une classe mère appelée “Vehicule”.
Attention : Ce n’est pas parce qu’un Vehicule possède un poids et qu’un EtreVivant aussi, qu’il faut forcément factoriser cette fonctionnalité. Il serait illogique de créer une classe mère pour les EtreVivant et pour les Vehicule à la fois. Il faut raisonner en termes de logique et de responsabilité.
Plus fort encore, il est possible de surcharger des méthodes ! Imaginez que pour tous les “EtreVivant”, la méthode getPv() doive retourner le nombre de point de vie de l’être vivant, mais qu’en ce qui concerne les “Dragon”, il faille retourner les pv + 100. Grâce à la surcharge, on va pouvoir redéfinir la méthode en question. Il suffira juste de réécrire notre méthode, avec le même nom/paramètres que dans la classe mère, et libre à nous de faire le traitement que l’on souhaite.
Les classes abstraites
Nous avons vu comment, grâce à l’héritage, factoriser les attributs/méthodes qui sont communs à plusieurs classes. Mais dans l’exemple des véhicules, il serait illogique d’instancier un objet qui soit juste de type “Vehicule”. Il faut forcément qu’il s’agisse d’une “Voiture” ou d’une “Moto”. Les classes abstraites permettent de corriger ça. En renseignant une classe comme telle, il est impossible d’instancier un objet de ce type uniquement. De plus, cela ajoute de la valeur d’un point de vue sémantique.
Les interfaces
Supposons que nous utilisons un langage de programmation objet fortement typé (Java, C++, …) : Comment faire pour lister tous les “EtreVivant” et afficher leur pv ? Si nous avons des “Humain”, devons-nous créer une liste juste pour les “Humain” ? De même pour les “Dragon” et les “Nain” ? Et bien non ! Grâce aux interfaces, nous allons pouvoir définir un modèle pour nos classes.
Quoi ? T’avais pourtant dis que les classes étaient comme des modèles pour nos objets ! Comment pourrait-on faire un modèle pour un modèle ?
Un exemple de code vaut parfois mieux qu’une longue explication :
Ici, nous avons créé une interface EtreVivant, qui permet de définir les méthodes que nos classes doivent implémenter. C’est à dire, une méthode appelée “getPoids” avec aucun argument, et qui retourne un float. C’est tout !
Nos classes, elles, s’annoncent comme respectant l’interface “EtreVivant” avec le mot clef “implements” (parfois deux points “ : ” suivant le langage, en C# par exemple). Pour que le programme fonctionne, elles doivent donc redéfinir la méthode “getPoids”.
Grâce à ça, nous pouvons réunir tous les humains et les dragons dans une liste de “EtreVivant”, et nous pourrons parcourir cette liste (for, foreach, …) et appeler la méthode “getPv” de chacun. Lorsqu’il s’agira d’”Humain”, ce seront les points de vie qui seront retournés, et lorsqu’il s’agira de “Dragon”, ce seront les points de vie + 100 qui seront retournés. Le programme pourra fonctionner, car même si dans notre boucle on ne sait pas à l’avance si un objet est un Humain ou un Dragon, on sait qu’il implémente l’interface “EtreVivant”, et donc qu’il a une méthode “getPv” qui ne prend aucun paramètre, et qui retourne un Integer.
De plus, une classe peut implémenter plusieurs interfaces, ce qui est impossible/déconseillé avec l’héritage. En effet, l’héritage multiple pose plusieurs problèmes qui ne seront pas énoncés ici.
Conclusion
Il existe un très grand nombre de langage de programme orientés objet. Chacun possède une syntaxe qui lui est propre. Mais généralement, ils suivent tous une même logique.
Vous retrouverez tous les termes et concepts ci-dessus dans quasiment tous les langages de programmation orientés objet utilisés de nos jours. Certaines petites subtilités vous demanderont d’adapter légèrement votre code, mais le fond reste le même.
L’avantage par rapport à la programmation procédurale ? Grâce à la POO, vous êtes en mesure de structurer votre code de manière à le rendre plus simple à maintenir, et plus facile à faire évoluer.
Pour résumer
Objet : Instance d’une classe. Comme un nouveau type de variable avec des fonctions/variables qui lui sont propres.
Classe : Modèle sur lequel sont basés des objets. Il s’agit de la « définition » d’un objet.
Visibilité : Publique, privée, ou protégée. Définit l’accessibilité de l’attribut/méthode.
Constructeur : Méthode exécutée lors de l’instanciation d’un nouvel objet.
Destructeur : Méthode exécutée lors de la suppression mémoire d’un objet.
Héritage : Procédé permettant de factoriser le code entre plusieurs classes ayant une logique en commun.
Classe abstraite : Une classe ayant pour but d’être héritée, et ne pouvant être instanciée.
Interface : Comme un contrat, permettant de définir des méthodes à implémenter pour nos classes.