-
Cours :
- Premiers pas en Java (pdf)
- Classes et objets (pdf)
- Penser objet et encapsulation (pdf)
- Tests et enum (pdf)
- Égalité, final, static, surcharge et documentation (pdf)
- Types paramétrés et interfaces (pdf)
- Agrégation, composition, délégation et extension (pdf)
- Paquetage, accessibilité et exceptions (pdf)
- Final et notions avancées (pdf)
- Documents :
- TP :
TP : agence de location
Consignes à suivre
Consignes pour démarrer le TP
Comme pour le TP 2, on va utiliser git pour la gestion de versions. Il vous faut donc vous reporter aux consignes du TP 2.
Une fois le dépôt téléchargé, vous pouvez compiler et exécuter le code cliquer deux fois sur agency
-> application
-> run
. Cela exécute la méthode
Pour exécuter les tests, il faut passer par l’onglet gradle à droite et cliquer deux fois sur agency
-> Tasks
-> verification
-> test
. Pour le moment, cela ne fonctionnera pas car les tests sont en commentaire.
Lien vers le projet gitlab à forker pour le TP : lien projet à forker
L’agence de location
Introduction
Une agence de location (rental agency en anglais) de vehicle (vehicles) offre à ses clients la possibilité de choisir la voiture qui souhaite louer en fonction de différents critères. L’agence propose la location de deux types de vehicles : des motos (motorbikes) et des voitures (cars). Vous allez donc créer deux classes Car
et Motorbike
qui implémenteront une interface Vehicle
.
Tâche 1 : interface Vehicle
Pour cette tâche, vous devez créer une interface
Vehicle
dans le packageagency
qui contient les méthodes suivantes :
String getBrand()
: renvoie la marque du véhicule sous la forme d’une chaîne de caractères.String getModel()
: renvoie le modèle du véhicule sous la forme d’une chaîne de caractères.int getProductionYear()
: renvoie l’année de fabrication du véhicule.double dailyRentalPrice()
: renvoie le prix de la location pour un jour du véhicule.boolean equals(Object o)
: teste l’égalité entre le véhicule et l’objeto
. Cette méthode devra renvoyer vrai sio
correspond au même véhicule quethis
. On considérera que deux véhicules sont égaux s’ils sont des instances de la même classe et qu’ils ont la même marque, le même modèle et la même année de production. Pour tester si deux objets ont la même classe ont pourra récupérer leur classe avec la méthodegetClass()
d’Object
et l’opérateur==
pour tester si deux classes sont égales.String toString()
: renvoie une chaîne de caractères qui comprend dans l’ordre et séparé par des espaces :- le type de véhicule : Motorbike ou Car
- la marque (brand) du véhicule,
- le modèle (model) du véhicule,
- l’année de fabrication (production year) du véhicule,
- des détails entre parenthèses sur le véhicule (spécifique à chaque type de véhicule),
- deux point
:
- le prix par jour du véhicule en euros suivi du caractère €.
Tâche 2 : classe Car
Pour cette tâche, vous aurez besoin de déterminer l’année courante. Pour récupérer la valeur de l’année en cours vous pouvez utiliser la méthode public static int currentYearValue()
de la classe TimeProvider
du package util
qui vous est fourni dans le projet.
Créez une classe
Car
à l’intérieur du packageagency
qui implémente l’interfaceVehicle
. Cette classe possédera les éléments suivants :
- un attribut
brand
de typeString
, - un attribut
model
de typeString
, - un attribut
productionYear
de typeint
, - un attribut
numberOfSeats
de typeint
et qui correspond au nombre de sièges de la voiture, - un constructeur
Car(String brand, String model, int productionYear, int numberOfSeats)
qui devra lever une exceptionIllegalArgumentException
(exception qui existe déjà en Java et donc pas besoin de la définir) dans les deux cas suivants :- si l’année donnée en argument est inférieure strictement à
1900
ou supérieur strictement à l’année en cours. Dans ce cas le message de l’exception devra contenir l’année de production donnée en argument du constructeur. - si le nombre de sièges est inférieur strictement à 1. Dans ce cas le message de l’exception devra contenir le nombre de siège donné en argument du constructeur.
String
donné au constructeur de l’exception devra indiquer pourquoi l’exception s’est produite. - si l’année donnée en argument est inférieure strictement à
- une méthode
String toString()
qui devra respecter le contrat défini dans l’interfaceVehicle
. Les détails d’une voiture à afficher entre parenthèse correspondront à son nombre de sièges. Par exemple, pour une voiture ayant 3 sièges il faudra que la chaîne contiennent(3 seats)
alors qu’elle devra contenir(1 seat)
si la voiture n’a qu’un siège. - une méthode
boolean isNew()
qui devra retournertrue
si la voiture a 5 ans ou moins (modèle récent) et faux sinon. Attention votre code devra fonctionner en utilisant l’année en cours : en 2021 les vieux modèles seront ceux datant d’au moins 2016 alors qu’en 2022, les voitures de 2016 seront considérées comme des vieux modèles. - une méthode
double dailyRentalPrice()
qui devra retourner le prix suivant la formule suivante : une voiture (vieux modèle) ayant plus de 5 ans coûtera 20 euros par siège alors qu’une voiture ayant 5 ans ou moins (modèle récent) coûtera 40 euros par siège.
Pour tester votre classe Car
, vous pouvez décommenter le code contenu dans la classe de test nommée TestCar
qui se trouve dans src
-> test
-> java
puis lancer les tests en cliquant deux fois sur agency-***
-> Tasks
-> verification
-> test
.
Tâche 3 : classes Motorbike
et AbstractVehicle
Créez une classe
Motorbike
à l’intérieur du packageagency
qui implémente l’interfaceVehicle
. Cette classe possédera les éléments suivants :
- un attribut
brand
de typeString
, - un attribut
model
de typeString
, - un attribut
productionYear
de typeint
, - un attribut
cylinderCapacity
de typeint
et qui correspond à la cylindrée en centimètre cube de la moto, - un constructeur
Motorbike(String brand, String model, int productionYear, int cylinderCapacity)
qui devra lever une exceptionIllegalArgumentException
(exception qui existe déjà en Java et donc pas besoin de la définir) dans les deux cas suivants :- si l’année donnée en argument est inférieure strictement à
1900
ou supérieur strictement à l’année en cours. - si la cylindrée est inférieure à 50.
String
donné au constructeur de l’exception devra indiquer pourquoi l’exception s’est produite. - si l’année donnée en argument est inférieure strictement à
- la méthode
String toString()
qui devra respecter le contrat défini dans l’interfaceVehicle
. Les détails d’une moto à afficher entre parenthèse avant le prix correspondront à sa cylindrée. Par exemple, pour une moto ayant un moteur de 500cm³ il faudra que la chaîne de caractères contienne (500cm³). - la méthode
double dailyRentalPrice()
devra retourner le prix suivant la formule suivante : une moto coûtera 0,25€ par centimètre cube de cylindrée.
Vous pouvez remarquer que la classe
Motorbike
a beaucoup de points communs avec la classeCar
. Afin d’éviter la duplication de code, nous vous conseillons l’une de faire l’une des deux choses suivantes (et seulement une, pas les deux) :
- créer une classe abstraite
AbstractVehicle
qui contiendra le code en commun des deux classes et qui sera étendue par les deux classesCar
etMotorbike
.- créer une interface
VehicleTypeSpecifics
implémentée par deux classes :CarSpecifics
etMotorbikeSpecifics
ainsi qu’une classeConcreteVehicle
implémentantVehicle
et qui utilisera la composition avecVehicleTypeSpecifics
pour coder le comportement spécifique aux voitures et aux motos.
Tâche 4 : RentalAgency
et UnknownVehicleException
Pour cette tâche, vous devez créer une classe
RentalAgency
à l’intérieur du packageagency
qui contiendra les éléments suivants (pour le moment) :
- un attribut
List<Vehicle> vehicles
qui est la liste des véhicules de l’agence, - un constructeur
RentalAgency()
qui construit une agence sans véhicules, - un constructeur
RentalAgency(List<Vehicle> vehicles)
qui construit une agence avec les véhicules contenus dans la liste spécifiée en argument, - une méthode
boolean add(Vehicle vehicle)
qui ajoute un véhicule à l’agence si celui-ci n’est pas déjà un véhicule de l’agence et qui ne fait rien sinon, cette méthode renvoietrue
si elle réussi à ajouter un véhicule etfalse
sinon, - une méthode
void remove(Vehicle vehicle)
qui enlève un véhicule à l’agence. Si le véhicule n’est pas un véhicule disponible à l’agence, une exception de typeUnknownVehicleException
devra être levée (exception définie ci-dessous), - une méthode
boolean contains(Vehicle vehicle)
qui teste si un véhicule est dans l’agence ou pas, c’est-à-dire qui renvoietrue
si le véhicule est dans l’agence etfalse
sinon. - une méthode
List<Vehicle> getVehicles()
qui renvoie la liste des véhicules de l’agence.
Vous devez aussi créer une classe
UnknownVehicleException
qui étendRuntimeException
et qui correspond à l’exception levée lorsqu’on essaie de supprimer un véhicule n’étant pas dans l’agence. Cette classe contiendra :
- un attribut
Vehicle vehicle
correspondant au véhicule qu’on a essayé d’enlever.- un constructeur
UnknownVehicleException(Vehicle vehicle)
évident,- la redéfinition de la méthode
String getMessage()
: cette méthode qui renverra une chaîne de caractère indiquant que le véhicule n’existe pas dans l’agence. Cette chaîne de caractères devra contenir la représentation sous forme de chaîne de caractères du véhicule (obtenu via un appel àtoString()
).
Pour tester votre classe RentalAgency
, vous pouvez décommenter le code correspondant dans la classe de test nommée TestRentalAgency
.
Tâche 5 : critères et filtres
On souhaite pouvoir sélectionner parmi les véhicules à louer toutes les véhicules satisfaisant un critère (criterion
) donné. Un critère peut être satisfait ou pas par un véhicule, cela signifie que le critère permet de filtrer (ou sélectionner) des véhicules ayant certaines propriétés. Ce concept existe déjà en java (depuis Java 1.8) sous la forme d’une interface générique Predicate<T>
qui correspond à l’interface suivante :
/**
* Represents a predicate (boolean-valued function) of one argument.
*
* @param <T> the type of the input to the predicate
*
*/
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
Cette interface permet donc de définir un critère (on dit aussi un prédicat) sur des objets d’un type T
quelconque. On peut tester le critère avec l’unique méthode abstraite test
de l’interface.
Le premier critère qu’on souhaite définir va nous permettre de filtrer les véhicules ayant une marque spécifique.
Écrire une classe
BrandCriterion
implémentant l’interfacePredicate<Vehicle>
.
Cette classe contiendra :
- un attribut
String brand
correspondant à la marque des véhicules qu’on souhaite sélectionner, - un constructeur
BrandCriterion(String brand)
évident, - une méthode
boolean test(Vehicle vehicle)
qui renvoietrue
si le véhicule a une marque égale à l’attributbrand
etfalse
sinon.
Écrivez une classe
MaxPriceCriterion
implémentant l’interfacePredicate<Vehicle>
qui permet de sélectionner les véhicules ayant un prix de location inférieur ou égal à un montant que l’on passera en paramètre du constructeur de la classe.
Ajoutez deux méthodes
select
etprintSelectedVehicles
dans la classeRentalAgency
qui remplissent les contrats suivants :
/**
* Returns the list of vehicles of this agency that satisfy the specified criterion
* The returned vehicles are then << filtered >> by the criterion.
*
* @param criterion the criterion that the selected cars must satisfy
* @return the list of cars of this agency that satisfy the given criterion
*/
public List<Vehicle> select(Predicate<Vehicle> criterion)
/**
* Prints the vehicles (one by line) of this agency that satisfy the specified criterion
*
* @param criterion the criterion that the selected cars must satisfy
*/
public void printSelectedVehicles(Predicate<Vehicle> criterion)
On peut naturellement souhaiter faire des intersections de critères, ce qui revient à appliquer le et logique entre les critères. On obtient alors un nouveau critère qui est satisfait si et seulement si tous les critères qui le composent sont satisfaits.
Écrivez une classe
IntersectionCriterion
qui permet de définir des critères par intersection de plusieurs critères.
Cette classe devra être générique avec un paramètre de type T
et devra implémenter l’interface Predicate<T>
. Elle aura en constructeur prenant en paramètre une liste de critères. Sa méthode test
retournera vrai si et seulement tous les critères passe le test.
Pour tester la méthode select
, vous pouvez décommenter le code correspondant dans la classe de test nommée TestRentalAgency
.
Tâche 6 : gestion des locations
On souhaite ajouter à la classe RentalAgency
la gestion de locations de véhicules par des clients. Un client ne peut louer qu’un véhicule à la fois.
Classe Client
Écrivez une classe
Client
qui permet de définir un client.
Un client aura une année de naissance, un nom de famille et un prénom. Il vous faudra générer les getters de cette classe ainsi que les méthodes equals
et hashCode
avec le menu generate d’IntelliJ.
Gestion de location dans la classe RentalAgency
Pour gérer des locations, vous allez utiliser une table (interface Map<K,V>
du package java.util
). Cette interface, qui est implémentée (en autre) par la classe HashMap<K,V>
, permet d’associer des clés (de type K
) à des valeurs (de type V
). Elle contient (entre autre) les méthodes suivantes :
boolean containsKey(Object key)
: Returnstrue
if this map contains a mapping for the specifiedkey
.V get(Object key)
: Returns the value to which the specifiedkey
is mapped, ornull
if this map contains no mapping for thekey
.V put(K key, V value)
: Associates (maps) the specifiedvalue
with the specifiedkey
in this map.Collection<V> values()
: Returns a Collection view of the values contained in this map.
Dans notre cas, les clients seront les clés et les véhicules qu’ils ont loué seront les valeurs. Une association clé/valeur correspondra donc à un client ayant loué un véhicule. Un client n’est donc présent dans cette table que si il est en train de louer un véhicule. Il en donc supprimé dès qu’il rend un véhicule.
Complétez la classe RentalAgency avec les éléments suivants :
- un attribut
Map<Client, Vehicle> rentedVehicles
qui contiendra les associations entre clients et véhicules loués. - une méthode
double rentVehicle(Client client, Vehicle vehicle) throws UnknownVehicleException, IllegalStateException
: permet au clientclient
de louer le véhiculevehicle
. Le résultat est le prix de location. L’exceptionUnknownVehicleException
est levée si le véhicule n’existe pas dans l’agence etIllegalStateException
est levée s’il est déjà loué ou que le client loue déjà un autre véhicule. - une méthode
boolean aVehicleIsRentedBy(Client client)
: renvoietrue
si et seulement siclient
est un client qui loue actuellement un véhicule et doncfalse
sinon. - une méthode
boolean vehicleIsRented(Vehicle v)
: renvoietrue
si et seulement si le véhicule est actuellement loué,false
sinon. - une méthode
void returnVehicle(Client client)
: le clientclient
rend le véhicule qu’il a loué. Il ne se passe rien s’il n’avait pas loué de véhicule. - une méthode
Collection<Vehicle> allRentedVehicles()
: renvoie la collection des véhicules de l’agence qui sont actuellement loués.
Pour tester la classe Client
, vous pouvez décommenter le code correspondant dans la classe de test nommée TestClient
.
Pour tester les nouvelles méthodes de RentalAgency
, vous pouvez décommenter le code correspondant dans la classe de test nommée TestRentalAgency
.
Tâches optionnelles
- Ajout de tests : Ajouter les tests manquants comme par exemple des tests pour la classe
Motorbike
. - Ajout de la javadoc : Ajouter une documentation java (de préférence en anglais) aux élément du codes pour lesquels cela vous semble utile.
- Nouveaux critères : rajoutez des classes supplémentaires pour les critères de sélection comme :
MinPriceCriterion
qui permet de sélectionner des véhicules coûtant au moins un prix.ModelCriterion
qui permet de sélectionner des véhicules ayant correspondant à un modèle.- …
- Sauvegarde/chargement depuis des fichiers : Modifiez votre code pour permettre la sauvegarde sous forme de fichier de la liste des véhicules d’une agence ainsi que le chargement depuis un fichier. L’idée serait de stocker les véhicules sous la forme de chaîne caractères avec les représentations des attributs et des types de véhicule séparés par un symbole spécial (par exemple
;
) et de mettre un véhicule par ligne du fichier. Vous pourrez utiliser la classeScanner
pour cela. - Prise en charge de rabais : on pourrait imaginer des agences qui gèrent des rabais. Pour cela, vous pouvez faire une classe
Discount
qui contiendrait par exemple des conditions sur le client et le véhicule, et le taux de rabais en pourcentage que l’agence appliquerait lors d’une location.