ENPC - Objets et Patterns - Devoir de programmation


À rendre au plus tard le 15 Mars 2002


DERNIÈRES PRÉCISIONS CONCERNANT LES DATES LIMITES

Il est impératif que vous rendiez le travail que vous avez fait, dans l'état où il est ce jour là, au plus tard le vendredi 15 Mars 2002.

Les élèves n'ayant rien rendu à cette date auront la note 0.

Néanmoins, une fois ce travail rendu le 15/03/2002, vous avez le droit de continuer à l'améliorer, le commenter, le documenter, etc, et d'envoyer votre travail amélioré (faculatif) jusqu'au lundi 25 Mars 2002, date après laquelle plus aucun document ne sera accepté.

Les travaux rendus entre le 16 Mars et le 25 Mars 2002 ne pourront être pris en compte qu'à l'unique condition qu'un travail ait déjà été rendu le 15 Mars.


L'objectif général et travail attendu

Le but de ce devoir de programmation est de vous faire réaliser individuellement un travail de réflexion, conception et développement d'une petite application en Java. Il vous est demandé, à l'issue de ce travail, de rendre au correcteur:

Le sujet

Le contexte de l'application est celui de la simulation d'un distributeur automatique d'articles, quels qu'ils soient. Des exemples courants sont les distributeurs de boissons ou de friandises. Ces distributeurs disposent en général d'un stock de différents articles, qui s'épuisent au fur et à mesure de la consommation par les clients. Ils acceptent les paiements des clients grâce à un monayeur, qui accepte des pièces et peut, dans la mesure du possible, rendre la monnaie si l'appoint n'était pas fait.

L'objectif de l'application que vous devez concevoir et réaliser est de simuler le comportement d'un tel distributeur, et de la manière la plus générique possible. Par exemple, votre application devra pouvoir être paramétrée par la description des pièces utilisables (reconnaissables par le monayeur): le passage du Franc à l'Euro est classique, mais l'application devra également fonctionner avec n'importe quelle monnaie, sous réserve de disposer de la description de l'ensemble des pièces. Autre exemple, le prix des articles vendus par le distributeur: l'application doit être paramétrée par la description des différents articles disponibles (intitulé, prix unitaire).

Enfin, on attend de l'application un minimum d'intelligence dans la gestion de son stock et de la monnaie. Pour le stock, elle doit savoir (et prévenir le client) lorsqu'un article est épuisé. On peut également imaginer qu'elle prévient le client lorsqu'elle n'a plus de monnaie et que celui-ci doit faire l'appoint.

Pour interragir avec l'application, il est possible, à terme, de développer une petite interface graphique. Mais on devra, dans un premier temps, pouvoir communiquer en mode texte avec l'application. Dans les deux cas, l'interaction se déroule de la manière suivante:

  1. l'application présente ses différents articles, leur prix et leur disponibilité (elle peut également préciser les pièces acceptées et/ou si elle dispose de monnaie ou non);
  2. l'utilisateur décrit la ou les pièces qu'il fournit, pour obtenir un crédit, puis demande l'article qui l'intéresse;
  3. en fonction du crédit accumulé par le monayeur et de l'article choisi, l'application distribue l'article et décrit la monnaie qu'elle a éventuellement à rendre (nombre et type de pièces);
  4. l'application est ensuite prête à retourner en 1.
Cette descritpion est un exemple: toute nouvelle idée pourra avantageusement être proposée, justifiée et mise en oeuvre par chacun d'entre vous.

Les problèmes spécifiques

Parmi les problèmes spécifiques que vous pouvez rencontrer, voici une liste de points sensibles auxquels vous devrez réfléchir ou sur lesquels vous pourrez rechercher des informations: Vous pouvez dès à présent réfléchir à ces différents problèmes. Cette page http://www-igm.univ-mlv.fr/~duris/ENPC/devoirProgrammation.html vous donnera bientôt quelques pistes ou quelques indications pour avancer. De même, nous aborderons en cours certains points de la programmation objet avec Java qui vous seront d'une grande utilité pour la réalisation de ce devoir de programmation.

Indication: JAR exécutable

Vous avez l'habitude de consulter la documentation des API à une adresse locale sur le disque du type file:///.../docs/api/index.html. Si vous regardez, dans le sous-répertoire de même racine docs, dans file:///...//docs/guide/jar/jar.html, vous trouverez toutes les indications nécessaires pour créer un jar exécutable.

Pour illustrer schématiquement un cas simple, supposons que vous voulez produire une archive exécutable de votre projet, correspondant à la méthode main de la classe MonProjet.java qui se trouve dans le paquetage projet, et qui utilise toutes les classes définies dans ce paquetage. La hierarchie de vos classes est alors la suivante (extrêmement simplifiée, c'est-à-dire que la classe MonProjet n'utilise que deux classes A et B:

home/projet/A.class
           /A.java
	   /B.class
           /B.java
           /MonProjet.class
           /MonProjet.java
Il vous faut dans ce cas créer une archive de toutes les classes nécessaires, c'est à dire tous les noms de fichier se terminant par .class situés dans le répertoire projet. En supposant que vous êtes placés dans le répertoire home, parent de projet, voici la ligne de commande à taper dans une fenêtre de commande (ou xterm) permettant de créer une archive de toutes les classes (l'option « c » est pour la création de l'archive, l'option « v » est pour un mode verbeux et l'option « f » permet de spécifier un nom de fichier pour l'archive créée (archive.jar).
home% jar cvf archive.jar ./projet/*.class
added manifest
adding: projet/A.class(in = 725) (out= 438)(deflated 39%)
adding: projet/B.class(in = 441) (out= 315)(deflated 28%)
adding: projet/MonProjet.class(in = 887) (out= 495)(deflated 44%)
Le mode verbeux permet d'obtenir des informations sur ce qui est fait: il ajoute tout d'abord un fichier manifest puis tous les fichiers terminant par *.class. Le résultat est la création d'un fichier de nom archive.jar. Néanmoins, cette archive n'est pas exécutable.

Pour qu'elle soit exécutable, cette archive doit être créée avec l'option « m » qui signifie que l'archive doit prendre en compte un fichier manifest (donné sur la ligne de commande) disant quelle est la classe dont la méthode main doit être exécutée.

Dans sa version la plus simple, vous pouvez créer un fichier de nom MANIFEST.MF qui contient simplement la ligne suivante:

Main-Class: projet.MonProjet
Le champ Main-Class spécifie le nom (complet) de la classe qui contient le main à exécuter; on suppose ici qu'il s'agit de la classe projet.MonProjet.

Avec la nouvelle hierarchie suivante sous le répertoire courant

home/projet/A.class
           /A.java
           /B.class
           /B.java
           /MonProjet.class
           /MonProjet.java
home/MANIFEST.MF
on peut donc exécuter la commande:
home% jar cvmf ./MANIFEST.MF archiveExecutable.jar ./projet/*.class
added manifest
adding: projet/A.class(in = 725) (out= 438)(deflated 39%)
adding: projet/B.class(in = 441) (out= 315)(deflated 28%)
adding: projet/MonProjet.class(in = 887) (out= 495)(deflated 44%)
qui produit une nouvelle archive, celle-ci exécutable, de nom archiveExecutable.jar.

N'importe qui, sous réserve de disposer d'une Machine Virtuelle Java, peut exécuter le programme dès qu'il dispose de cet unique fichier, en exécutant la commande:

home% java -jar archiveExecutable.jar [arguments]
[arguments] représente des arguments optionnels (si le main de la classe MonProjet exécuté en accepte).

Dans notre exemple, nous avons inclus uniquement les fichiers .class dans l'archive, mais il est possible d'y placer n'importe quel type de fichier.

Si le destinataire de ce fichier d'archive désire désarchiver son contenu, il peut le faire avec l'option « x » d'extraction à la place de l'option « c » de création. Il peut également obtenir la table du contenu de l'archive avec l'option « t ». Taper la commande seule jar affiche toutes les options que celle-ci accepte.

Dans notre exemple, si l'utilisateur dispose du fichier archiveExecutable.jar dans un répertoire vide tmp, il peut en connaître le contenu comme ceci:

home/tmp% jar tvf archiveExecutable.jar
     0 Tue Mar 05 15:23:02 CET 2002 META-INF/
    98 Tue Mar 05 15:23:04 CET 2002 META-INF/MANIFEST.MF
   725 Tue Mar 05 11:40:14 CET 2002 projet/A.class
   441 Tue Mar 05 11:40:14 CET 2002 projet/B.class
   887 Tue Mar 05 11:40:14 CET 2002 projet/MonProjet.class
et extraire ce contenu grâce à la commande suivante:
home/tmp% jar xvf archiveExecutable.jar
  created: META-INF/
extracted: META-INF/MANIFEST.MF
extracted: projet/A.class
extracted: projet/B.class
extracted: projet/MonProjet.class


Indication: organisation en paquetages

Toutes les indications relatives à l'organisation de vos classes en paquetages sont dans le poly « Objets », aux paragraphes 1.8 et 4.9.

Brièvement, on peut dire qu'un paquetage (paquet ou package) est le regroupement d'un ensemble de classes et d'un ensemble de sous-paquetages. Ce regroupement correspond à une cohésion d'implémentation et d'utilisation logicielle forte entre les éléments qu'il contient. Les exemples des API standards sont nombreux: le paquetage java.util contient toutes les collections et beaucoup de classes utilitaires; le paquetage java.io contient les classes ayant trait aux entrées sorties; le paquetage java.lang contient les classes primordiales du langage Java, mais aussi des sous paquetages, comme java.lang.reflect, qui contient des classes offrant les mécanismes de réflection du langage Java.

On indique qu'une classe A fait partie d'un paquetage pak.souspak (elle fait partie du paquetage de nom souspak, lui-même appartenant au paquetage de nom pak) en faisant figurer au début du fichier contenant la classe A (en général le fichier A.java) l'instruction package pak.souspak;

En l'absence d'une telle instruction, les classes sont réputées appartenir au paquetage anonyme.

On s'attache à faire en sorte que les noms de répertoire et de fichiers soient parfaitement isomorphes aux noms de paquetages et de classes. Par exemple, dans l'indication sur les Jar exécutables ci-dessus, nous avons trois classes, MonProjet, A et B qui font partie du paquetage projet. Cela signifie en particulier que le fichier contenant le code source de la classe MonProjet débute de la manière suivante:

package projet;
public class MonProjet {
  ...
}
Imaginons maintenant que le paquetage projet contienne deux sous-paquetages, pak1 contenant les classes C et D et pak2 contenant les classes E et F. Nous aurons alors l'arborescence de fichiers et de répertoires suivante:
home/projet/A.class
           /A.java
           /B.class
           /B.java
           /MonProjet.class
           /MonProjet.java
home/projet/pak1/C.class
                /C.java
		/D.class
		/D.java
home/projet/pak2/E.class
                /E.java
		/F.class
		/F.java			   
home/MANIFEST.MF
Si on suppose que la classe E doit utiliser la classe Vector, elle devra faire un import java.util.Vector;. De même, si elle doit utiliser une classe d'un autre paquetage, comme C par exemple, elle devra également faire un import projet.pak1.C; ou encore import projet.pak1.*; pour pouvoir utiliser toutes les classes de projet.pak1. L'autre alternative consiste à utiliser, dans le programme source, le nom complet de la classe, c'est-à-dire préfixé par les paquetages qui la contiennent. Voici par exemple le programme source (imaginaire) du fichier E.java:
package projet.pak2;       // Déclare le paquetage d'appartenance de E

import projet.pak1.C;      // Permet d'utiliser C sans la préfixer
import java.util.*;        // Permet d'utiliser toutes les classes de
                           // java.util sans les préfixer.

public class E {
  // on peut utiliser java.util.Vector tel quel
  Vector v = new Vector();  
  public void m() {
    // on peut utiliser projet.pak1.C tel quel
    C c = new C();
    // mais on est obligé d'utiliser le nom complet 
    // de projet.pak1.D qui n'a pas été importé
    projet.pak1.D d = new projet.pak1.D()
    ...
  }
}

Pour compiler, il est important de se placer à la racine de la hierarchie de paquetages, c'est-à-dire à la racine de l'arborescence (au niveau du répertoire parent de projet). Par exemple, la compilation de la classe E.java doit se faire par:

home% javac projet/pak2/E.java
ce qui a pour effet de placer le fichier E.class à sa place dans /home/projet/pak2/.

Si cette classe dispose d'une méthode main, son exécution peut être demandée en donnant le nom complet de cette classe:

home% java projet.pak2.E


Etienne.Duris[at]univ-mlv.fr - © École Nationale des Ponts et Chaussées - Mars 2002 - http://www-igm.univ-mlv.fr/~duris/ENPC/