Programmation objet
Introduction
Avec Java, la notion d’objet est omniprésente et nécessite un minimum d’apprentissage. Nous allons donc voir dans un premier temps les principes de la programmation objet et le vocabulaire associé, puis nous verrons comment mettre cela en application avec Java.
Dans un langage procédural classique, le fonctionnement d’une application est réglé par une succession d’appels aux différentes procédures et fonctions disponibles dans le code. Ces procédures et fonctions sont chargées de manipuler les données de l’application qui sont représentées par les variables de l’application. Il n’y a aucun lien entre les données et le code qui les manipule. Dans un langage objet, on va au contraire essayer de regrouper le code. Ce regroupement est appelé une classe. Une application développée avec un langage objet est donc constituée de nombreuses classes représentant les différents éléments manipulés par l’application. Les classes vont décrire les caractéristiques de chacun des éléments. C’est ensuite l’assemblage de ces éléments qui va permettre le fonctionnement de l’application.
Ce principe est largement utilisé dans d’autres domaines que l’informatique. Dans l’industrie automobile, par exemple, il n’existe certainement pas, chez aucun constructeur, un plan complet décrivant les milliers...
Mise en œuvre avec Java
1. Contexte
Dans le reste de ce chapitre, nous allons travailler sur la classe Personne et ses sous-classes. La représentation UML (Unified Modeling Language) simplifiée de cette hiérarchie de classe est disponible ci-dessous.
UML est un langage graphique dédié à la représentation des concepts de programmation orientée objet. Pour plus d’informations sur ce langage, vous pouvez consulter l’ouvrage UML 2.5 dans la collection Ressources Informatiques des Éditions ENI.
2. Création d’une classe
La création d’une classe passe par la déclaration de la classe elle-même et de tous les éléments la constituant.
a. Déclaration de la classe
La déclaration d’une classe se fait en utilisant le mot-clé class suivi du nom de la classe puis d’un bloc de code délimité par les caractères { et }. Dans ce bloc de code, on trouve des déclarations de variables qui seront les champs de la classe et des fonctions qui seront les méthodes de la classe. Plusieurs mots-clés peuvent être ajoutés pour modifier les caractéristiques de la classe. La syntaxe générale de déclaration d’une classe est donc la suivante :
[modificateurs] class NomDeLaClasse
[extends NomDeLaClasseDeBase]
[implements NomDeInterface1,NomDeInterface2,...]
{
Code de la classe
}
Les signes [ et ] sont utilisés ici pour indiquer le caractère optionnel de l’élément. Ils ne doivent pas être utilisés dans le code de déclaration d’une classe.
Les modificateurs permettent de déterminer la visibilité de la classe et la manière de l’utiliser. Voici la liste des modificateurs disponibles :
public : indique que la classe peut être utilisée par toutes les autres classes. Sans ce modificateur, la classe ne sera utilisable que par les autres classes faisant partie du même package. Pour plus d’informations sur les packages, veuillez vous référer à la section éponyme un peu plus loin dans le chapitre....
Les packages
1. Présentation
Le but principal des packages est l’organisation et le rangement des classes et interfaces d’une application. Le principe est le même que dans la vie courante. Si vous avez un grand placard dans votre maison, il est évident que l’installation d’étagères dans ce placard va faciliter l’organisation et la recherche des objets qui y sont rangés par rapport à un stockage en "vrac". L’organisation des classes en packages apporte les avantages suivants :
-
Facilité pour retrouver et réutiliser un élément.
-
Limitation des risques de conflits de noms.
-
Création d’une nouvelle visibilité (la visibilité package) en plus des visibilités standards (private, protected, public). Lorsqu’un élément (une classe, un champ, une méthode) ne présente pas de modificateurs de visibilité, alors cet élément n’est visible que par les éléments présents dans le même package. Il existe une subtilité pour les éléments protégés (champs, méthodes). Ils sont bien sûr accessibles dans les classes dérivées, mais aussi dans les classes du même package.
2. Création d’un package
La première chose à faire lorsque l’on souhaite créer un package est de lui trouver un nom. Ce nom doit être choisi avec précaution pour permettre au package de remplir pleinement son rôle. Il doit être unique et représentatif des éléments stockés à l’intérieur.
Pour assurer l’unicité du nom d’un package, on utilise par convention le nom de domaine de l’entreprise en inversant l’ordre des éléments comme première partie pour le nom du package.
Par exemple, si le nom de domaine est eni.fr, la première partie du nom du package sera fr.eni. Si le nom de domaine comporte des caractères interdits pour les noms de packages, ils sont remplacés par le caractère _. Ainsi pour le nom de domaine eni-ecole.fr, la première partie du nom de package sera fr.eni_ecole. Cette première partie du nom permet d’assurer...
Les modules
1. Mise en place
Lors de la création d’un projet Java avec Eclipse, la fenêtre suivante apparaît pour demander la création d’un fichier nommé module-info.java :
Cette étape est liée à l’apparition de la notion de modules avec Java 9. Cliquez sur Create et un fichier module-info.java est créé dans le répertoire racine contenant les sources. Son contenu est le suivant :
module Projet_JavaSE_Chapitre3_Modules {
}
2. Présentation
Depuis Java 9, l’API Java a été modularisée. L’objectif est multiple. Le principal intérêt de la modularisation de l’API est le gain en performance lors de l’exécution des programmes. En effet, la modularisation permet à la machine virtuelle de ne charger que les modules nécessaires au bon fonctionnement des programmes qu’elle exécute.
Ceci peut sembler superflu avec les capacités de calcul des machines actuelles, mais si l’on se penche sur l’Internet des objets (IOT - Internet Of Things), ceci prend tout son sens.
Les autres intérêts que l’on peut relever sont les suivants :
-
Une plateforme modulaire est plus facilement maintenable et donc évolutive.
-
Une gestion plus précise des éléments mis à disposition. En effet, jusqu’à présent, dès qu’une classe était nécessaire au-delà du package...
La gestion des erreurs
Dans la vie d’un développeur, tout n’est pas rose ! Les erreurs sont une des principales sources de stress. En fait, lorsque l’on y regarde de plus près, nous pouvons classer ces erreurs qui nous gâchent la vie en trois catégories. Regardons chacune d’entre elles et les solutions disponibles pour les traiter.
1. Les différents types d’erreurs
a. Les erreurs de syntaxe
Ce type d’erreur se produit au moment de la compilation lorsque le développeur fait une faute de frappe. Très fréquentes avec les outils de développement où l’éditeur de code et le compilateur sont deux entités séparées, elles deviennent de plus en plus rares avec les environnements de développement intégré (Eclipse, NetBeans, Jbuilder...). La plupart de ces environnements proposent une analyse syntaxique au fur et à mesure de la saisie du code.
Si une erreur de syntaxe est détectée, alors l’environnement propose des solutions possibles pour corriger cette erreur.
D’autre part, les "fautes d’orthographe" dans les noms de champs ou de méthodes sont facilement éliminées grâce aux fonctionnalités disponibles dans ces environnements.
b. Les erreurs d’exécution
Ces erreurs apparaissent après la compilation lorsque vous lancez l’exécution de votre application. La syntaxe du code est correcte, mais l’environnement de votre application ne permet pas l’exécution d’une instruction utilisée dans votre application. C’est par exemple le cas si vous essayez d’ouvrir un fichier qui n’existe pas sur le disque de votre machine. Vous obtiendrez sûrement un message de ce type :
Ce type de message n’est pas très sympathique pour l’utilisateur !
Heureusement, Java permet la récupération de ce type d’erreur et évite ainsi l’affichage de cet inquiétant message. Nous détaillerons cela un peu plus loin dans ce chapitre.
c. Les erreurs de logique
Ce sont les pires ennemies des développeurs. Tout se compile sans problème, tout s’exécute sans erreurs et pourtant "ça ne marche pas comme prévu" !!!
Il faut dans ce cas revoir la logique...
Les génériques
1. Présentation
Les types génériques sont des éléments d’un programme qui s’adaptent automatiquement pour réaliser la même fonctionnalité sur différents types de données. Lorsque vous créez un élément générique, vous n’avez pas besoin de concevoir une version différente pour chaque type de donnée avec lequel vous souhaitez réaliser une fonctionnalité. Pour faire une analogie avec un objet courant, nous allons prendre l’exemple d’un tournevis. En fonction du type de vis que vous avez à utiliser, vous pouvez prendre un tournevis spécifique pour ce type de vis (plat, cruciforme, torx...). Une technique fréquemment utilisée par un bricoleur averti consiste à acquérir un tournevis universel avec de multiples embouts. En fonction du type de vis, il choisit l’embout adapté. Le résultat final est le même que s’il disposait d’une multitude de tournevis différents : il peut visser et dévisser.
Lorsque vous utilisez un type générique, vous le paramétrez avec un type de données. Ceci permet au code de s’adapter automatiquement et de réaliser la même action indépendamment du type de données. Une alternative pourrait être l’utilisation du type universel Object. L’utilisation des types génériques présente plusieurs avantages par rapport à cette solution :
-
Elle impose la vérification des types de données au moment de la compilation et évite les inévitables vérifications qui doivent être faites manuellement avec l’utilisation du type Object.
-
Elle évite les opérations de conversion du type Object vers un type plus spécifique et inversement.
-
L’écriture du code est facilitée dans certains environnements de développement avec l’affichage automatique de tous les membres disponibles pour un type de données particulier.
-
Elle favorise l’écriture d’algorithmes qui sont indépendants des types de données.
Les types génériques peuvent cependant imposer certaines restrictions concernant le type de données utilisé....
Les collections
1. Présentation
Les applications ont très fréquemment besoin de manipuler de grandes quantités d’informations. De nombreuses structures sont disponibles pour faciliter la gestion de ces informations. Elles sont regroupées sous le terme collection. Comme dans la vie courante, il y a différents types de collections et de collectionneurs. Il peut y avoir des personnes qui récupèrent toutes sortes de choses, mais qui n’ont pas d’organisation particulière pour les ranger, d’autres qui sont spécialisées dans la collection de certains types d’objets, les maniaques qui prennent toutes les précautions possibles pour pouvoir retrouver à coup sûr un objet...
Le langage Java propose différentes classes permettant de mettre en place plusieurs modes de gestion.
La première solution pour gérer un ensemble d’éléments est l’utilisation de tableaux. Cette solution a été décrite dans la section Les tableaux du chapitre Bases du langage. Bien que simple à mettre en œuvre, cette solution n’est pas très souple. Son principal défaut tient au caractère fixe de la taille d’un tableau. S’il n’y a plus de place pour stocker des éléments supplémentaires, il faut créer un nouveau tableau plus grand et y transférer le contenu du tableau précédent. Cette solution, lourde à mettre en œuvre, est consommatrice de ressources.
Le langage Java propose une vaste palette d’interfaces et de classes pour gérer facilement des ensembles d’éléments. Les interfaces décrivent les fonctionnalités disponibles alors que les classes implémentent et fournissent réellement ces fonctionnalités. Suivant le mode de gestion des éléments souhaité, on utilisera bien sûr la classe la plus adaptée. Cependant, les mêmes fonctionnalités de base doivent être accessibles, quel que soit le mode de gestion. Pour assurer la présence de ces fonctionnalités indispensables dans toutes les classes, celles-ci ont été regroupées dans l’interface Collection qui est par la suite utilisée comme interface de base.
La hiérarchie...
Exercices
1. Exercice 1
Créer une classe représentant un article d’un magasin de vente par correspondance. Un article est caractérisé par sa référence, sa désignation, son prix. Créer ensuite une méthode main permettant de tester le bon fonctionnement de la classe précédente.
2. Exercice 2
Ajouter les deux classes Livre et Dvd héritant de la classe Article.
Un livre possède un numéro ISBN, contient un certain nombre de pages et a été écrit par un auteur, un DVD a une certaine durée et a été produit par un réalisateur.
Ajouter les attributs nécessaires aux classes Livre et Dvd pour avoir le nom de l’auteur ou du réalisateur. Tester ensuite le fonctionnement de ces deux nouvelles classes.
3. Exercice 3
Modifier les classes Livre et Dvd pour avoir disponibles les informations suivantes concernant l’auteur ou le réalisateur :
-
son nom
-
son prénom
-
sa date de naissance
Indice : les auteurs et les réalisateurs sont des personnes.
4. Exercice 4
Modifier le code précédent pour pouvoir obtenir rapidement la liste des articles concernant un auteur ou un réalisateur.
Corrections
1. Correction de l’exercice 1
La classe Article est relativement simple. Les lignes 5 à 7 contiennent la déclaration des attributs représentant les informations que l’on souhaite mémoriser dans les instances de la classe. Ceux-ci sont déclarés avec la visibilité private pour garantir que seul le code de la classe pourra y accéder. Vient ensuite la déclaration des constructeurs (lignes 9 à 29). Les différentes versions permettent la création d’instances en fonction des informations disponibles. Pour éviter la duplication de code, les constructeurs peuvent s’appeler mutuellement (lignes 16, 21, 27).
Bien qu’il soit possible d’utiliser directement les attributs dans les constructeurs, il est préférable d’utiliser les méthodes setXXX pour y accéder afin de profiter des éventuelles vérifications effectuées sur les valeurs à affecter aux attributs.
Pour rendre les attributs accessibles de l’extérieur de la classe, chacun doit avoir une méthode getXXX et setXXX permettant d’obtenir la valeur de l’attribut ou de lui affecter une valeur.
La méthode toString permet d’obtenir une représentation sous forme d’une chaîne de caractères de l’instance de la classe.
La méthode main crée ensuite deux instances de la classe Article, soit en utilisant le constructeur par défaut et en initialisant ensuite les attributs un à un (lignes 9 à 12), soit en utilisant le constructeur correspondant à la liste des attributs disponibles (ligne 14). Les deux instances sont ensuite affichées soit par l’appel de la fonction test qui permet de définir le format d’affichage, soit en utilisant la méthode toString.
Dans ce cas, le formatage est imposé par la classe Article.
/********************* Article **********************/
1. package fr.eni.editions.exercice1;
2.
3. public class Article
4. {
5. private int reference;
6. private String designation;
7. private double prix;
8.
9. public Article()
10. { ...