Coucou ! Ça t'est déjà arrivé de te demander pourquoi Java était conçu comme il est ? Je veux dire, tu déclares des classes et crées des objets basés sur les classes, les classes ont des méthodes, etc.
Mais pourquoi le langage est structuré de telle sorte que les programmes se composent de classes et d'objets plutôt qu'autre chose ? Comment le concept d'un «
objet » a-t-il émergé et été adopté ? Tous les langages sont-ils conçus de cette façon ? Et si ce n'est pas le cas, quels avantages Java tire-t-il de ce paradigme ?
Comme tu peux le voir, ça nous fait beaucoup de questions :) Nous allons essayer de répondre à chacune dans la leçon du jour.
Qu'est-ce que la programmation orientée objet Java (POO) ?
La première chose à savoir est que Java ne se compose pas d'objets et de classes juste pour le plaisir. Cela n'est pas arrivé sur un coup de tête des créateurs de Java, et ce n'est même pas leur invention. Il y a beaucoup d'autres langages basés sur les objets.
Le premier langage de ce type à avoir vu le jour est
Simula, qui a été inventé dans les années 60 en Norvège. Les concepts d'une «
classe » et d'une «
méthode » sont apparus avec Simula.
Si on regarde les normes actuelles de développement de logiciels,
Simula peut paraître antique, mais tout le monde peut voir l'« air de famille » qu'il partage avec la POO Java.
Tu peux probablement lire facilement le code écrit dans ce langage et expliquer dans les grandes lignes ce qu'il fait :)
Begin
Class Rectangle (Width, Height); Real Width, Height;
Begin
Real Area, Perimeter;
Procedure Update;
Begin
Area := Width * Height;
OutText("Rectangle is updating, Area = "); OutFix(Area,2,8); OutImage;
Perimeter := 2*(Width + Height);
OutText("Rectangle is updating, Perimeter = "); OutFix(Perimeter,2,8); OutImage;
End of Update;
Update;
OutText("Rectangle created: "); OutFix(Width,2,6);
OutFix(Height,2,6); OutImage;
End of Rectangle;
Rectangle Class ColouredRectangle (Color); Text Color;
Begin
OutText("ColouredRectangle created, color = "); OutText(Color);
OutImage;
End of ColouredRectangle;
Ref(Rectangle) Cr;
Cr :- New ColouredRectangle(10, 20, "Green");
End;
Cet exemple de code est tiré de
Simula - 50 years of OOP.
Comme tu peux le voir, Java n'est pas très différent de son langage grand-parent :)
En effet, l'apparition de Simula a marqué la naissance d'un nouveau paradigme : la
programmation orientée objet.
Wikipedia définit la POO ainsi : «
La programmation orientée objet (POO) est un paradigme de programmation informatique. Il consiste en la définition et l'interaction de briques logicielles appelées objets ; un objet représente un concept, une idée ou toute entité du monde physique, comme une voiture, une personne ou encore une page d'un livre. » Je trouve que c'est une très bonne définition.
Il n'y a pas si longtemps que tu as commencé à apprendre Java, mais cette définition ne contient probablement pas de mots que tu ne connais pas :)
Aujourd'hui, la
POO est la méthodologie de programmation la plus commune.
En plus de Java, les
principes de la POO sont utilisés dans de nombreux langages populaires dont tu as peut-être entendu parler. Par exemple C++ (activement utilisé dans le développement de jeux), Objective-C et Swift (utilisés pour l'écriture de programmes pour les appareils Apple), Python (le plus populaire pour l'apprentissage automatique), PHP (une des langues de développement Web les plus populaires), JavaScript (il serait plus rapide de dire à quoi il ne sert pas), et bien d'autres.
Alors, que sont ces fameux « principes » de la POO ? Nous allons t'expliquer ça en détail.
Principes de la POO
C'est la base de la base. Les 4 piliers qui forment ensemble le paradigme de la programmation orientée objet. Tu te dois de les comprendre pour t'en sortir dans ta carrière dans la programmation.
Principe 1. Héritage
Bonne nouvelle : tu connais déjà quelques-uns des principes de la POO ! :) Nous avons déjà mentionné l'héritage deux fois lors des leçons, et nous avons même réussi à l'utiliser.
L'
héritage est un mécanisme qui te permet de décrire une nouvelle classe à partir d'une classe existante (son parent). Ce faisant, la nouvelle classe emprunte les propriétés et fonctionnalités de la classe parent.
Qu'est-ce que l'héritage et quels avantages offre-t-il ?
La réutilisation du code avant tout. Les champs et méthodes déclarés dans les classes parent peuvent être utilisés dans les classes descendantes.
Si tous les types de voiture ont 10 champs en commun et 5 méthodes identiques, tu peux simplement les déplacer dans une classe parent
Car. Ensuite, tu peux les utiliser dans les classes descendantes sans aucun problème.
Des avantages solides : à la fois quantitatifs (moins de code) et, par conséquent, qualitatifs (les classes deviennent beaucoup plus simples).
De plus, l'héritage est très flexible : tu peux écrire les fonctionnalités supplémentaires qui manquent aux descendants (certains champs ou comportements qui sont spécifiques à une classe particulière).
Comme dans la vraie vie, nous sommes tous un peu semblables à nos parents, mais aussi un peu différents d'eux :)
Principe 2. Abstraction
C'est un principe très simple. L'abstraction signifie identifier les caractéristiques essentielles, les plus importantes de quelque chose, tout en rejetant tout ce qui est mineur et insignifiant.
Pas besoin de réinventer la roue. Rappelons-nous d'un exemple d'une vieille leçon de nos cours.
Supposons que nous créons un système de classement pour les employés d'une entreprise. Pour créer des objets « employés », nous avons écrit une classe
Employee. Quelles caractéristiques sont importantes pour les décrire dans le système de classement de l'entreprise ? Un
nom, une
date de naissance, un
numéro de sécurité sociale et un
ID d'employé. Mais il est peu probable que nous aurons besoin de la taille de l'employé, de la couleur de ses yeux ou de celle de ses cheveux pour ce type d'enregistrement. L'entreprise n'a pas besoin de ces informations au sujet d'un employé.
Dans la classe
Employee, nous déclarons les variables suivantes :
String name,
int age,
int socialSecurityNumber et
int employeeId.
Et nous faisons abstraction des informations inutiles comme la couleur des yeux. Cependant, si nous faisons un système d'archivage pour une agence de mannequins, la situation change radicalement. La
taille, la
couleur des yeux et la
couleur des cheveux sont des caractéristiques importantes des mannequins, mais leur numéro de sécurité sociale ne nous concerne nullement.
Ainsi, dans la classe
Model, nous créons les variables suivantes :
String height,
String hair,
String eyes.
Principe 3. Encapsulation
Nous avons déjà rencontré ce principe. En Java, l'encapsulation signifie la restriction de la capacité à lire et modifier des données.
Comme tu peux le voir, le terme est basé sur le mot « capsule ». Nous allons utiliser une « capsule » pour cacher les données importantes que nous ne voulons pas que d'autres puissent changer.
Voici un exemple simple tiré de la vie réelle. Tu as un prénom et un nom. Tous tes amis les connaissent. Mais ils n'ont pas la possibilité de changer ton prénom ou ton nom. On peut dire que le processus de changement de nom est « encapsulé » par le système judiciaire : tu ne peux changer ton nom de famille que par le biais d'un greffier, et tu es la seule personne à pouvoir lancer la procédure. Les autres « utilisateurs » ont un accès en « lecture seule » à ton prénom et ton nom :)
Un autre exemple illustratif est l'argent que tu gardes chez toi. Le laisser bien en évidence au milieu de ta chambre n'est pas une super idée. Tout « utilisateur » (toute personne qui visite ta maison) sera en mesure de modifier le montant d'argent que tu as en se servant. Il serait préférable de l'encapsuler dans un coffre-fort. Ainsi, l'accès ne sera disponible que pour toi, et seulement en utilisant un code spécial.
Les exemples d'encapsulation évidents avec lesquels tu as déjà travaillé sont les modificateurs d'accès (
private,
public, etc.), ainsi que les accesseurs.
Si tu n'encapsules pas le champ
age de la classe
Cat, alors n'importe qui peut écrire :
Cat.age = -1000;
Nous utilisons l'encapsulation pour protéger le champ
age avec une méthode setter, où l'on peut s'assurer que l'âge ne peut pas être un nombre négatif.
Principe 4. Polymorphisme
Le
polymorphisme est la capacité de travailler avec plusieurs types comme s'ils étaient du même type. En plus de cela, le comportement de chaque objet sera différent en fonction de son type.
Ça te semble compliqué ? Un petit exemple t'aidera à comprendre.
Prenons l'exemple le plus simple : les animaux. Créé une classe
Animal avec une seule méthode
speak(), et deux sous-classes :
Cat et
Dog.
public class Animal {
public void speak() {
System.out.println("Hello!");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println ("Woof-woof!");
}
}
public class Cat extends Animal {
@Override
public void speak() {
System.out.println("Meow!");
}
}
Maintenant, nous allons déclarer une variable de référence
Animal et lui affecter un objet
Dog.
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.speak();
}
}
À ton avis, quelle méthode sera appelée ?
Animal.speak() ou
Dog.speak() ?
La méthode de la classe Dog sera appelée :
Ouaf ouaf !
Nous avons créé une référence
Animal, mais l'objet comme se comporte un
Dog. Si nécessaire, nous pourrions avoir des objets qui se comportent comme un chat, un cheval ou un autre animal. L'important est d'attribuer une sous-classe spécifique à la variable de référence plus générale
Animal. C'est logique, car tous les chiens sont des animaux. C'est ce que nous avions à l'esprit quand nous avons dit que « le comportement de chaque objet sera différent en fonction de son type. »
Si nous avions créé un objet
Cat...
public static void main(String[] args) {
Animal cat = new Cat();
cat.speak();
}
La méthode speak() aurait affiché « Miaou »
Mais qu'est-ce que nous entendons par « capacité de travailler avec plusieurs types comme s'ils étaient du même type » ?
C'est assez simple, là encore.
Imaginons que nous créons un salon de toilettage. Notre salon de toilettage devrait être en mesure de s'occuper de tout animal, donc nous créons une méthode
trim() avec une méthode qui reçoit un paramètre
Animal (l'animal qui va avoir droit à une coupe).
public class AnimalBarbershop {
public void trim(Animal animal) {
System.out.println("The haircut is done!");
}
}
Et maintenant nous pouvons passer des objets
Cat et
Dog à la méthode
trim() !
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
AnimalBarbershop barbershop = new AnimalBarbershop();
barbershop.trim(cat);
barbershop.trim(dog);
}
Et voici l'exemple clair : la classe
PetGroomingSalon interagit avec les types
Cat et
Dog comme s'ils étaient du même type. Cela étant,
Cat et
Dog ont des comportements différents, à savoir qu'ils parlent chacun différemment.
Pourquoi avons-nous besoin de la POO ?
Pourquoi la
POO a émergé comme nouveau paradigme de programmation ?
Les programmeurs avaient des outils qui fonctionnaient, comme les langages procéduraux. Qu'est-ce qui les a poussés à inventer quelque chose de fondamentalement nouveau ?
Avant tout, ce fut la complexité des tâches auxquelles ils étaient confrontés.
S'il y a 60 ans la tâche du programmeur était quelque chose du genre « évaluer une expression mathématique », maintenant ça ressemble plus à « implémenter 7 fins différentes pour le jeu S.T.A.L.K.E.R. en fonction de la combinaison des décisions du joueur aux points A, B, C, D, E et F dans le jeu. »
Comme tu peux le voir, les tâches sont devenues clairement plus compliquées au cours des dernières décennies. En conséquence, les types de données sont eux aussi devenus plus compliqués. C'est une autre raison de l'apparition de la POO.
Une expression mathématique peut être évaluée facilement avec des primitives ordinaires. Aucun objet n'est nécessaire pour cela. Mais la tâche impliquant les fins de jeu serait difficile à ne serait-ce que décrire sans l'aide de classes personnalisées.
Cela dit, la tâche est assez facile à décrire en utilisant des classes et des objets. De toute évidence, nous aurons besoin de plusieurs classes :
Game,
Stalker,
Ending,
PlayerDecision,
GameEvent, et ainsi de suite. En d'autres termes, même sans commencer à résoudre le problème, nous pouvons facilement « esquisser » une solution dans notre tête.
La complexité croissante des tâches contraint les programmeurs à les diviser en plusieurs parties. Mais ce n'est pas si facile à faire en programmation procédurale. Très souvent, un programme est comme un arbre avec beaucoup de branches représentant tous les chemins d'exécution possibles. Selon certaines conditions, une branche du programme ou une autre est exécutée.
Pour les petits programmes, cela fonctionnait bien, mais il était très difficile de diviser un grand problème en plusieurs parties. Ce fut encore une autre raison de l'émergence de la POO.
Ce nouveau paradigme a donné aux programmeurs la possibilité de diviser un programme en un tas de « modules » (classes) qui font chacun leur propre partie du travail. En interagissant les uns avec les autres, tous les objets accomplissent le travail de notre programme.
De plus, nous pouvons réutiliser notre code ailleurs dans le programme, ce qui permet aussi d'économiser beaucoup de temps.
Cet article est également disponible en anglais. |
Read the English version of this article for an introduction to OOP principles.
|
GO TO FULL VERSION