:: Enseignements :: Licence :: L3 :: 2025-2026 :: Programmation Objet avec Java ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) |
Interface et Collection
|
Exercice 1 - Manifeste d'un porte conteneur
Cet exercice reprent l'écriture des classes mutables
comme Library dans le TP précédent. Le nouveau concept que nous allons explorer est le polymorphisme.
Un porte-conteneur (container en anglais) est un bateau qui, comme son nom l'indique,
transporte des conteneurs d'un port à l'autre. Chaque porte-conteneur possède un manifeste
(manifest), qui est un document papier contenant une liste de l'ensemble des conteneurs
qu'il transporte.
Dans ce TP, nous allons modéliser ce document papier.
Vous écrirez toutes les classes de ce TP dans un package nommé
fr.uge.manifest. Vous devez tester toutes les méthodes demandées
et vous écrirez tous vos tests dans la classe Application
du package fr.uge.manifest.application.
Dans un premier temps, on cherche à définir un
Container.
Un conteneur possède un code BIC (
bic), un poids (
weight), une valeur entière en kg,
ainsi qu'une destination (
destination) sous forme de chaîne de caractères.
Écrire le type
Container de telle façon à ce que le code suivant fonctionne:
public static void main(String[] args) {
var container1 = new Container("DSVX 123456 5", 500, "Germany");
System.out.println(container1.bic()); // DSVX 123456 5
System.out.println(container1.weight()); // 500
System.out.println(container1.destination()); // Germany
...
Rappel : Comme vous le savez maintenant, il ne doit pas être possible de créer un conteneur avec des valeurs invalides : le code BIC doit exister,
le poids doit être positif ou nul et la destination doit exister.
Ce doit être un automatisme pour vous et à partir de maintenant, les sujets ne le mentionneront plus explicitement.
On veut maintenant introduire la notion de
Manifest, un manifeste contient une liste de conteneurs.
Pour l'instant, un manifeste définit une méthode
add(conteneur) qui permet d'ajouter
un conteneur au manifeste.
Il ne doit pas être possible d'ajouter un conteneur
null.
Écrire le type
Manifest tel que le code suivant fonctionne:
public static void main(String[] args) {
...
var container2 = new Container("MSCU 789012 3", 400, "Italy");
var container3 = new Container("ONEZ 345678 2", 200, "Austria");
var manifest1 = new Manifest();
manifest1.add(container2);
manifest1.add(container3);
}
De même qu'il ne doit pas être possible de créer un objet représentant un état invalide, les méthodes publiques des objets doivent vérifier que les paramètres sont valides.
A l'avenir, les sujets ne le mentionneront plus explicitement.
Un porte conteneur comme son nom ne l'indique pas peut aussi transporter des passagers.
Un
Passenger est défini par un nom (
name) et une destination (
destination).
Dans un premier temps, comment définir un
Passenger tel que l'on puisse créer un passager.
Puis expliquer comment modifier
Manifest pour que l'on puisse enregistrer
aussi bien des conteneurs que des passagers.
Écrire le code de
Passenger et modifier le code de
Manifest tel que le code
ci-dessous fonctionne.
public static void main(String[] args) {
...
var passenger1 = new Passenger("Nicolas F", "France");
var container4 = new Container("OOCL 098765 0", 350, "England");
var manifest2 = new Manifest();
manifest2.add(passenger1);
manifest2.add(container4);
On souhaite ajouter une méthode
totalPrice à
Manifest qui calcul coût total pour transporter tous les conteneurs et tous les passagers du bateau.
Le prix pour un passager est 10.
Le prix pour un conteneur est le poids du conteneur multiplié par 2.
Ajouter une méthode
totalPrice à
Manifest et faite en sorte que le prix
soit calculés correctement.
System.out.println(manifest2.price()); // 710
On souhaite maintenant pouvoir afficher un manifeste.
Afficher un manifeste revient à afficher chaque conteneur/passager sur une ligne,
avec un numéro, 1 pour le premier conteneur/passager, 2 pour le suivant, etc.
Chaque ligne est suivie d'un retour à la ligne, y compris après la dernière ligne.
Pour le formatage exact, vous pouvez regarder l'exemple.
Modifier le type
Manifest pour que le code suivant ait le comportement attendu:
public static void main(String[] args) {
...
var manifest3 = new Manifest();
manifest3.add(new Container("OOCL 098765 0", 350, "England"));
manifest3.add(new Passenger("Jane D", "US"));
System.out.println(manifest3);
// 1. OOCL 098765 0 350kg to England
// 2. Jane D to US
}
Il arrive que l'on soit obligé de décharger tous les conteneurs liés à une destination
s'il y a des problèmes d'embargo (quand un dictateur se dit qu'il s'offrirait bien une partie
d'un pays voisin par exemple). Dans ce cas, il faut connaitre tous les conteneurs et passagers
liés à cette destination au niveau du manifeste.
Pour prendre en compte cela, on introduit une méthode
toDestination(destination)
qui renvoie une liste de tous les containers/passagers allant à ayant la destination
passée en paramètre
Quelle est le type de retour de
toDestination(destination) ?
Pourquoi ?
Modifier le code pour introduire cette méthode pour que l'exemple ci-dessous fonctionne:
public static void main(String[] args) {
...
var manifest4 = new Manifest();
manifest4.add(new Container("HAPC 543210 3", 450, "Russia"));
manifest4.add(new Container("BICU 123456 5", 200, "China"));
manifest4.add(new Container("CMAU 432109 6", 125, "Russia"));
manifest4.add(new Passenger("Ana K","Russia"));
var embargoed = manifest4.toDestination("Russia");
System.out.println(embargoed);
// [HAPC 543210 3 450kg to Russia, CMAU 432109 6 125kg to Russia, Ana K to Russia]
On souhaite maintenant détecter que le manifeste est valide, c'est-à-dire qu'il n'existe pas
deux passagers ayant le même nom ou deux conteneurs ayant le même
cid ou
un passager et un conteneur ayant le même identifiant.
Pour cela, on se propose de créer une méthode
checkIsInvalid qui lève une exception
IllegalStateException si le manifeste est invalide.
var manifest5 = new Manifest();
manifest5.add(new Passenger("James Bond", "UK"));
manifest5.add(new Passenger("James Bond", "Iceland"));
manifest5.checkIsInvalid(); // boom !
var manifest6 = new Manifest();
manifest6.add(new Container("HLLY 345678 5", 30, "Slovenia"));
manifest6.add(new Container("HLLY 345678 5", 40, "France"));
manifest6.checkIsInvalid(); // boom !
Quelle est la complexité pire cas, si l'on implante
checkIsInvalid en faisant deux boucles
imbriquées sur les passagers ou conteneurs ?
On se propose plutôt d'utiliser l'interface
Set, l'implantation
HashSet et
la valeur de retour de la méthode
add(element).
Décrire en français l'algorithme que l'on doit utiliser ?
Quelle est la complexité pire cas ?
Implanter la méthode
checkIsInvalid
En fait, avoir une méthode checkIsInvalid est vraiment un mauvais design, le bon design
est de vérifier que l'on ne peut pas créer un manifeste invalide plutôt que de permettre de créer un manifeste
invalide et se poser la question s'il est valide ou non après.
Commenter la méthode checkIsInvalid et modifier la méthode add pour lever l'exception
IllegalStateException dès que l'on essaye d'ajouter un passager ou un conteneur qui
va rendre le manifeste invalide.
Note: l'approche qui consiste à checker que l'on ne peut pas créer un objet invalide plutôt que de vérifier
à postériori qu'un objet est invalide, est référencé en anglais par les 3 mots parse don't validate.
[Revision] Pour les plus balèzes,
On met les conteneurs ayant la même destination au même endroit sur le porte-conteneur, et si
un porte-conteneur est mal équilibré il a une fâcheuse tendance à se retourner. Donc, pour aider au placement
des conteneurs, il doit être possible de fournir un dictionnaire qui, pour chaque destination, indique
le poids de l'ensemble des conteneurs liés à cette destination.
Pour cela, écrire une méthode
weightPerDestination qui, pour un manifeste donné, renvoie un dictionnaire
qui indique le poids des conteneurs pour chaque destination.
Par exemple, avec le code ci-dessous, il y a deux conteneurs qui ont comme destination "Monaco", avec un
poids combiné de 100 + 300 = 400, tandis que "Luxembourg" a un seul conteneur de poids 200.
public static void main(String[] args) {
...
var manifest7 = new Manifest();
manifest7.add(new Container("BICU 123456 7", 100, "Monaco"));
manifest7.add(new Container("CXSB 987654 9", 200, "Luxembourg"));
manifest7.add(new Container("EYRA 321098 6", 50, "Paris"));
manifest7.add(new Container("DNVN 543210 8", 300, "Monaco"));
manifest7.add(new Passenger("Dimitri From", "Paris"));
System.out.println(manifest7.weightPerDestination());
// {Monaco=400, Luxembourg=200, Paris=50}
}
Note: si vous encore plus balèze, il existe une méthode
map.merge() dans l'interface
Map
qui peut simplifier votre implantation.
© Université de Marne-la-Vallée