Version
- 1.0 (05/2023)
Statut
Terminé
Contexte du projet
Ce projet est une revisite d’une réalisation effectuée dans le cadre de ma formation Openclassrooms. Après avoir complété le travail initial, disponible sur Github, j’ai décidé de le reprendre pour le transformer en un projet personnel. J’y ai ajouté des fonctionnalités, et amélioré le code pour le rendre plus propre et plus moderne. Le tout se déroule dans l’espace cette fois, parce que c’est plus fun !
Etant mon second projet en Java, il m’a permis de consolider mes acquis et d’approfondir mes connaissances sur la manipulation des bases de données en code natif.
Gallerie
Fonctionnalités
- Support multilingue : Prise en charge de l’anglais et du français.
- Gestion des quais d’amarrage : Suivi et gestion des emplacements des vaisseaux.
- Tarification dynamique : Calcul du prix en fonction de la durée de l’amarrage et du type de vaisseau.
- Première demi-heure gratuite : Les trente premières minutes d’amarrage sont offertes.
- Réduction pour clients réguliers : Application automatique d’une réduction de 10% après 5 amarrages.
Stack technique
L’objectif étant de reprendre un projet existant, la stack était déjà définie. J’ai donc continué à utiliser Java côté serveur et MySQL comme système de gestion de base de données.
Cependant, j’ai choisi d’adopter Gradle à la place de Maven pour la gestion du build, ce qui m’a permis de découvrir une nouvelle approche et d’élargir mes compétences.
Organisation du code
L’organisation suit une architecture en couche pour garantir une séparation claire des responsabilités et une maintenabilité optimale du code. La structure du projet est la suivante :
Nom du package | Responsabilité |
---|---|
Config | Centralise les configurations de l'application. |
Constants | Regroupe les constantes des requêtes SQL, les modalités de tarification, etc. |
Service | Contient la logique métier de l'application. |
DAO | Gère les interactions avec la base de données. |
Model | Définit les entités et objets métiers utilisés dans l'application. |
Util | Regroupe des fonctions utilitaires et des classes d'assistance pour la lecture de la console, la gestion des dates, des langues, etc. |
En parallèle, un dossier resources
contient les fichiers de configuration pour la base de données, les logs et les fichiers de langues.
Structure de la base de données

La base de données est très simple et ne comporte que deux tables : docks
et tickets
. La première stocke les informations relatives aux quais d’amarrage avec dock_number pour ID, tandis que la seconde contient les données des tickets.
---------------------------------------------------
-- Créer la table des quais d'amarrage
---------------------------------------------------
CREATE TABLE docks (
DOCK_NUMBER INT PRIMARY KEY,
AVAILABLE TINYINT NOT NULL,
TYPE VARCHAR(10) NOT NULL
);
---------------------------------------------------
-- Créer la table des tickets
---------------------------------------------------
CREATE TABLE tickets (
ID INT PRIMARY KEY AUTO_INCREMENT,
DOCK_NUMBER INT NOT NULL,
SHIP_NAME VARCHAR(10) NOT NULL,
PRICE DOUBLE,
IN_TIME DATETIME NOT NULL,
OUT_TIME DATETIME,
FOREIGN KEY (DOCK_NUMBER) REFERENCES docks (DOCK_NUMBER)
);
Développement
L’un des aspects clés de ce projet est la gestion de la tarification, qui dépend du type de vaisseau et de la durée d’amarrage. Bien que la solution idéale eût été de stocker les tarifs horaires dans une base de données, le projet initial utilisait des constantes codées en dur. J’ai donc choisi de conserver cette approche, mais en cherchant une technique plus élégante et maintenable pour les gérer. C’est à cette occasion que j’ai découvert la puissance des Enum en Java.
J’ai donc pu établir ceci :
public enum DockType {
CORVETTE(1.0),
DESTROYER(1.5),
CRUISER(2.0),
TITAN(2.5),
COLOSSUS(3.0);
private final double ratePerHour;
DockType(double ratePerHour) {
this.ratePerHour = ratePerHour;
}
public double getRatePerHour() {
return ratePerHour;
}
}
L’énumération DockType
définit les différents types de vaisseaux et associe à chacun un taux horaire entre parenthèses. Par exemple, un vaisseau de type CORVETTE a un taux horaire de 1.0, tandis qu’un vaisseau de type COLOSSUS a un taux horaire de 3.0. Cette information est stockée dans un attribut ratePerHour
.
Le constructeur DockType
permet d’initialiser l’attribut ratePerHour
avec la valeur passée en paramètre. La méthode getRatePerHour
permet de récupérer le taux horaire associé à un type de vaisseau.
Ainsi, chaque type de vaisseau est associé à un taux horaire, ce qui permet de simplifier le calcul de la tarification et de garantir une meilleure maintenabilité du code, puisqu’au moment du calcul, il suffit de récupérer le taux horaire associé au type de vaisseau comme ceci :
public void calculateFare(Ticket ticket, boolean discount) {
LocalDateTime inTime = ticket.getInTime();
LocalDateTime outTime = ticket.getOutTime();
long durationInMinutes = Duration.between(inTime, outTime).toMinutes();
double durationInHours = roundDecimals(durationInMinutes / 60.0, 2);
if (durationInHours <= MAX_HOURS_FREE_DOCKING) return;
double ratePerHour = ticket.getDockSpot().getDockType().getRatePerHour();
double price = durationInHours * ratePerHour;
if (discount) price *= RECURRING_USER_DISCOUNT_PERCENTAGE;
ticket.setPrice(roundDecimals(price, 2));
}
La méthode calculateFare
accepte deux paramètres : un objet ticket
représentant l’amarrage du vaisseau, et un booléen discount
indiquant si le client est éligible à une réduction. Elle commence par récupérer les dates d’entrée et de sortie du vaisseau, puis calcule la durée d’amarrage en heures. La durée est arrondie à deux décimales pour éviter les imprécisions liées aux calculs en virgule flottante grâce à la fonction utilitaire roundDecimals
.
Ensuite, on vérifie si la durée est inférieure ou égale à 30 minutes, auquel cas l’amarrage est gratuit. Sinon, on récupère le taux horaire associé au type de vaisseau grâce à l’énumération et on calcule le prix en multipliant la durée par ce taux.
Si le client est un habitué, une réduction de 10% est appliquée.
Enfin, on arrondit le prix à deux décimales et on le stocke dans l’objet ticket
.
La durée d’amarrage gratuit et le taux de réduction sont des constantes définies dans la classe Discount
, ce qui permet de les modifier facilement si besoin.
Cette approche a considérablement simplifié le code d’origine qui concernait deux types de véhicules dans un parking classique. Elle permet également d’ajouter de nouveaux types de vaisseaux sans avoir à modifier le code existant, ce qui rend le système plus évolutif et plus facile à maintenir.
D’abord l’énumération sous exploitée :
public enum ParkingType {
CAR,
BIKE
}
Ensuite, le taux horaire dans une classe séparée :
public class Fare {
public static final double BIKE_RATE_PER_HOUR = 1.0;
public static final double CAR_RATE_PER_HOUR = 1.5;
}
Enfin, le calcul de la tarification :
public void calculateFare(Ticket ticket, boolean discount) {
LocalDateTime inTime = ticket.getInTime();
LocalDateTime outTime = ticket.getOutTime();
long durationInMinutes = Duration.between(inTime, outTime).toMinutes();
double durationInHours = roundDecimals(durationInMinutes / 60.0, 2);
if (durationInHours <= MAX_HOURS_FREE_PARKING) return;
double price;
switch (ticket.getParkingSpot().getParkingType()) {
case CAR: {
price = durationInHours * Fare.CAR_RATE_PER_HOUR;
break;
}
case BIKE: {
price = durationInHours * Fare.BIKE_RATE_PER_HOUR;
break;
}
}
if (discount) price *= RECURRING_USER_DISCOUNT_PERCENTAGE;
ticket.setPrice(roundDecimals(price, 2));
}