Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1325 minIntermédiaire

Exercices UML corrigés type BTS SIO

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • traiter un cas complet de cas d'utilisation avec sa description ;
  • construire un diagramme de classes à partir d'un texte ;
  • faire évoluer un diagramme et coder la méthode demandée ;
  • lire un diagramme fourni pour écrire et sécuriser du code, comme à l'E7.

6 exercices corrigés2 schémas

Ce chapitre rassemble six exercices UML corrigés du niveau du BTS SIO SLAM, chacun sur un contexte différent, comme dans les sujets : un diagramme de cas d'utilisation avec sa description, un diagramme de classes construit à partir d'un texte, l'évolution d'un diagramme existant, un diagramme de séquence tiré d'un code JavaScript, une machine à états avec son code C#, et une mini-étude de cas de type E7 où le diagramme sert à écrire et sécuriser du code. Chaque corrigé est complet et justifié. Fais chaque exercice sur papier, en temps limité, avant d'ouvrir la réponse.

CasDiagrammesDurée conseillée
1. Location de vélosCas d'utilisation, description textuelle40 min
2. Covoiturage d'entrepriseClasses à partir d'un texte45 min
3. Parc informatiqueFaire évoluer un diagramme de classes, coder une méthode30 min
4. Réservation de sallesSéquence à partir d'un code JavaScript30 min
5. Boutique en ligneÉtats-transitions, code et tests35 min
6. Mutuelle, type E7Lire un diagramme, écrire et sécuriser du code45 min

Cas 1. Location de vélos : cas d'utilisation et description

Exercice 1 sur 6 Cas 1 : VéloLib Réussi À revoir

L'agence VéloLib loue des vélos électriques. Voici l'expression des besoins de sa future application.

« Un client crée son compte en ligne, puis réserve un vélo pour une date et une durée ; la réservation exige le paiement d'un acompte de 20 % par carte, traité par la plateforme de paiement en ligne. Au moment de réserver, le client peut ajouter une assurance casse. Il peut annuler une réservation jusqu'à la veille. Au retour du vélo, un agent de l'agence enregistre le retour ; s'il constate un dommage, il le déclare, et la déclaration est transmise à l'assureur. Le responsable de l'agence consulte les statistiques de location. Clients, agents et responsable se connectent avec un identifiant et un mot de passe. »

  1. Dessine le diagramme de cas d'utilisation.
  2. Rédige la description textuelle du cas « Réserver un vélo » : préconditions, scénario nominal, scénarios alternatifs et d'erreur, postconditions.
Voir la réponseMasquer la réponse de l'exercice 1

1. Le diagramme

VéloLibCréer son compteRéserver un véloAnnuler uneréservationSouscrire l'assurancecassePayer l'acompteS'authentifierEnregistrer le retourd'un véloDéclarerun dommageConsulter lesstatistiques«include»«extend»«extend»ClientUtilisateurAgentResponsable«actor»Plateformede paiement«actor»Assureur
Corrigé du cas 1 : les cas d'utilisation de VéloLib

Justifications :

  • Payer l'acompte est inclus dans la réservation : il est obligatoire à chaque réservation. Il a son propre acteur secondaire, la plateforme de paiement.
  • Souscrire l'assurance casse et Déclarer un dommage sont des extensions : optionnelles, sous condition (le client le souhaite ; un dommage est constaté). Les flèches partent de l'extension.
  • Annuler une réservation est un cas indépendant, pas une extension de la réservation : c'est un autre objectif, plus tard.
  • Créer son compte est relié au Client seulement : les comptes des agents et du responsable sont créés autrement (hors périmètre, faute d'information dans le texte).
  • Les trois acteurs humains héritent d'Utilisateur, qui porte « S'authentifier ».

2. La description de « Réserver un vélo »

Acteur principal : Client. Acteur secondaire : Plateforme de paiement.

Préconditions : le client est authentifié.

Scénario nominal

  1. Le client choisit une date, une heure de début et une durée.
  2. Le système affiche les vélos disponibles sur ce créneau, avec le prix de la location.
  3. Le client choisit un vélo.
  4. Le système affiche le récapitulatif : vélo, créneau, prix total et acompte de 20 %.
  5. Le client confirme et saisit ses coordonnées bancaires sur la page de la plateforme de paiement.
  6. La plateforme de paiement confirme le paiement de l'acompte.
  7. Le système enregistre la réservation, rend le vélo indisponible sur le créneau et envoie la confirmation au client.

Scénarios alternatifs

  • 4a. Le client ajoute l'assurance casse : le système ajoute son prix au total et recalcule l'acompte. Retour à l'étape 4.
  • 3a. Le client modifie le créneau : retour à l'étape 1.

Scénarios d'erreur

  • 2a. Aucun vélo n'est disponible : le système le signale et propose les créneaux libres les plus proches. Retour à l'étape 1 ou fin du cas.
  • 6a. Le paiement est refusé : le système n'enregistre pas la réservation, affiche le refus et propose de réessayer (retour à l'étape 5).
  • 7a. Le vélo a été réservé par un autre client entre les étapes 3 et 7 : le système annule le paiement de l'acompte, prévient le client et revient à l'étape 2.

Postconditions : la réservation est enregistrée et payée à 20 % ; le vélo est indisponible sur le créneau ; le client a reçu une confirmation.

Le scénario 7a est celui qu'on oublie le plus souvent : deux clients qui réservent le même vélo au même moment. Il impose une vérification au moment de l'enregistrement (une transaction, côté base de données).

Tu avais trouvé ?

Cas 2. Covoiturage d'entreprise : diagramme de classes

Exercice 2 sur 6 Cas 2 : CoVoit'Pro Réussi À revoir

Une entreprise répartie sur plusieurs sites veut une application de covoiturage entre salariés.

« Chaque salarié a un nom, un prénom, une adresse électronique et un site de rattachement. Un site a un nom et une adresse. Un salarié peut déclarer un ou plusieurs véhicules (immatriculation, modèle, nombre de places passagers). Un salarié conducteur propose des trajets : chaque trajet a une date, une heure de départ, un site de départ et un site d'arrivée, et utilise l'un des véhicules du conducteur ; il propose un nombre de places, au plus égal au nombre de places passagers du véhicule. D'autres salariés réservent une place sur un trajet ; une réservation a une date et un statut (demandée, acceptée, refusée, annulée). Un salarié ne peut pas réserver deux fois le même trajet. L'application calcule le nombre de places restantes d'un trajet et permet au conducteur d'accepter ou de refuser une réservation. »

Dessine le diagramme de classes : classes, attributs typés, opérations principales, associations avec multiplicités et rôles.

Voir la réponseMasquer la réponse de l'exercice 2
Site- nom : string- adresse : stringSalarie- nom : string- prenom : string- email : stringVehicule- immatriculation : string- modele : string- nbPassagers : intReservation- date : DateOnly- statut : StatutResa+ Accepter()+ Refuser()Trajet- date : DateOnly- heureDepart : TimeOnly- nbPlaces : int+ PlacesRestantes() : int+ Reserver(p : Salarie) : Reservation«enumeration»StatutResaDemandeeAccepteeRefuseeAnnulee10..*10..*1conducteur0..*0..*passagers0..*10..*11departarrivee0..*0..*{nbPlaces <= vehicule.nbPassagers}le véhicule appartient au conducteur
Corrigé du cas 2 : le diagramme de classes de CoVoit'Pro

Justifications :

  • Deux associations entre Trajet et Salarie : l'une pour le conducteur (1 côté salarié : un trajet a un seul conducteur), l'autre pour les passagers. Les rôles les distinguent ; sans eux, le diagramme serait ambigu.
  • Deux associations entre Trajet et Site, rôles depart et arrivee, pour la même raison.
  • Reservation est une classe-association : la date et le statut appartiennent au couple trajet-passager. La règle « un salarié ne peut pas réserver deux fois le même trajet » est exactement ce que garantit une classe-association (un seul lien par couple). Si l'on voulait garder l'historique d'une réservation annulée puis refaite, il faudrait une classe ordinaire.
  • Le statut est une énumération, StatutResa : quatre valeurs fixes.
  • PlacesRestantes() est une opération (un attribut dérivé /placesRestantes serait aussi juste) : nbPlaces moins le nombre de réservations acceptées.
  • La contrainte sur le nombre de places ne se dessine pas par une multiplicité : une note ou une contrainte {nbPlaces <= vehicule.nbPassagers}, vérifiée dans le constructeur de Trajet.
  • Le véhicule utilisé doit appartenir au conducteur : c'est une autre contrainte, à écrire dans une note, que le code vérifie.
  • Pas d'héritage Conducteur / Passager : un même salarié est conducteur un jour et passager le lendemain. Ce sont des rôles, pas des sous-classes.

Tu avais trouvé ?

Cas 3. Parc informatique : faire évoluer un diagramme

Exercice 3 sur 6 Cas 3 : le parc de la mairie Réussi À revoir

Le service informatique d'une mairie gère son parc avec une application dont voici le diagramme de classes (extrait) :

  • Agent : - matricule : string, - nom : string ;
  • Service : - nom : string ;
  • Materiel {abstract} : - numInventaire : string, - dateAchat : DateOnly, - prixAchat : decimal ; + DureeAmortissement() : int {abstract} ;
  • Ordinateur et Imprimante héritent de Materiel ;
  • Agent "0..*" ──── "1" Service ; Materiel "0..*" ──── "0..1" Agent (un matériel est affecté à au plus un agent ; en stock, il n'est affecté à personne).

La mairie veut désormais suivre les interventions sur le matériel : chaque intervention a une date, une durée en minutes, une description et un technicien (un agent du service informatique) ; elle concerne un seul matériel ; un matériel peut avoir plusieurs interventions. Les interventions doivent être conservées même si le technicien quitte la mairie.

  1. Complète le diagramme.
  2. Écris en C# la méthode TempsInterventions(debut : DateOnly, fin : DateOnly) : int de Materiel, qui renvoie le total des durées des interventions sur ce matériel entre deux dates incluses.
  3. Pourquoi DureeAmortissement() est-elle abstraite ?
Voir la réponseMasquer la réponse de l'exercice 3

1. L'évolution

  • Nouvelle classe Intervention : - date : DateOnly, - dureeMinutes : int, - description : string.
  • Materiel "1" ──── "0..*" Intervention : une intervention concerne un matériel ; un matériel a zéro ou plusieurs interventions. Une composition (losange plein côté Materiel) est défendable : l'historique des interventions n'a pas de sens sans le matériel. Une association simple est aussi juste.
  • Intervention "0..*" ──── "1" Agent, rôle technicien côté Agent : une association simple, surtout pas une composition : l'intervention survit au départ du technicien (il faudra donc ne pas supprimer l'agent, mais le désactiver).
  • On peut noter dans une note : « le technicien appartient au service informatique ».

2. La méthode

csharp
public int TempsInterventions(DateOnly debut, DateOnly fin)
{
    int total = 0;
    foreach (Intervention i in interventions)
    {
        if (i.Date >= debut && i.Date <= fin)
        {
            total += i.DureeMinutes;
        }
    }
    return total;
}

Le chemin suit l'association 0..* vers Intervention : une boucle. Les deux bornes sont incluses (>= et <=), comme le demande l'énoncé. Avec trois interventions de 45, 30 et 90 minutes les 3 mars, 15 avril et 2 mai 2026, TempsInterventions(new DateOnly(2026, 3, 1), new DateOnly(2026, 4, 30)) renvoie 75.

3. Chaque type de matériel a sa propre durée d'amortissement (un ordinateur ne s'amortit pas comme une imprimante), et un « matériel » générique n'en a pas. L'opération est donc déclarée dans la classe mère, pour que tout matériel puisse répondre, et redéfinie dans chaque fille. Le code qui calcule la valeur du parc appelle DureeAmortissement() sur chaque matériel sans connaître son type.

Tu avais trouvé ?

Cas 4. Réservation de salles : séquence à partir d'un code JavaScript

Exercice 4 sur 6 Cas 4 : l'API de réservation Réussi À revoir

L'API Node.js d'une application de réservation de salles contient le code suivant (Express 5). La route reçoit une demande de réservation, vérifie la disponibilité, enregistre et répond.

js
// routes/reservations.js
router.post('/reservations', async (req, res) => {
  const { salleId, debut, fin } = req.body;
  const resultat = await serviceReservation.reserver(req.utilisateur.id, salleId, debut, fin);
  if (resultat.ok) {
    res.status(201).json(resultat.reservation);
  } else {
    res.status(409).json({ erreur: resultat.erreur });
  }
});

// services/serviceReservation.js
async function reserver(utilisateurId, salleId, debut, fin) {
  const conflits = await depotReservations.chercherConflits(salleId, debut, fin);
  if (conflits.length > 0) {
    return { ok: false, erreur: 'Salle déjà réservée sur ce créneau' };
  }
  const reservation = await depotReservations.inserer({ utilisateurId, salleId, debut, fin });
  await notifications.envoyer(utilisateurId, `Salle ${salleId} réservée`);
  return { ok: true, reservation };
}
  1. Dessine le diagramme de séquence de la requête POST /reservations, depuis le front React jusqu'à la base, avec ses deux issues.
  2. L'envoi de la notification peut échouer (serveur de messagerie indisponible). Que se passe-t-il pour l'utilisateur avec ce code ? Que proposes-tu ?
Voir la réponseMasquer la réponse de l'exercice 4

1. Le diagramme (en texte, au format PlantUML)

text
@startuml
participant "Front React" as front
participant "routeur" as route
participant "serviceReservation" as service
participant "depotReservations" as depot
database "MySQL" as bd
participant "notifications" as notif

front -> route : POST /reservations {salleId, debut, fin}
route -> service : reserver(utilisateurId, salleId, debut, fin)
service -> depot : chercherConflits(salleId, debut, fin)
depot -> bd : SELECT … (réservations qui chevauchent)
bd --> depot : lignes
depot --> service : conflits
alt conflits non vides
  service --> route : {ok: false, erreur}
  route --> front : 409 Conflict {erreur}
else aucun conflit
  service -> depot : inserer(reservation)
  depot -> bd : INSERT INTO reservation …
  bd --> depot : id
  depot --> service : reservation
  service -> notif : envoyer(utilisateurId, message)
  notif --> service
  service --> route : {ok: true, reservation}
  route --> front : 201 Created {reservation}
end
@enduml

Le if … else du service devient un fragment alt ; les await sont des appels synchrones au sens du diagramme (le code attend la réponse avant de continuer) ; le code 409 Conflict signale un conflit avec l'état de la ressource (codes HTTP).

2. Si notifications.envoyer lève une erreur, l'await la propage : la route ne répond pas 201, Express renvoie une erreur 500, alors que la réservation est enregistrée en base. L'utilisateur croit que la réservation a échoué et risque de recommencer. Deux corrections possibles : attraper l'erreur de notification (try … catch), la journaliser et répondre quand même 201 ; ou, mieux, envoyer la notification de façon asynchrone (la déposer dans une file traitée à part), ce qui se dessine par une flèche à pointe ouverte sans retour. Le diagramme de séquence rend ce défaut visible : la notification est dans le chemin critique de la réponse.

Tu avais trouvé ?

Cas 5. Boutique en ligne : machine à états, code et tests

Exercice 5 sur 6 Cas 5 : le cycle de vie d'une commande Réussi À revoir

Une commande d'une boutique en ligne est en attente de paiement à sa création. Quand le paiement est accepté, elle est payée ; si le paiement n'arrive pas sous 48 heures, elle est annulée. Une commande payée est préparée par l'entrepôt, puis expédiée avec un numéro de suivi, puis livrée quand le transporteur le confirme. Le client peut annuler une commande payée tant qu'elle n'est pas expédiée : elle est alors annulée et remboursée. Une commande livrée ou annulée n'évolue plus.

  1. Donne les états et les transitions (Départ → Arrivée : événement [garde] / effet).
  2. Écris en C# la classe Commande avec ses transitions.
  3. Liste les tests unitaires à écrire.
Voir la réponseMasquer la réponse de l'exercice 5

1. La machine à états

États : EnAttentePaiement, Payee, Preparee, Expediee, Livree, Annulee.

  • ● → EnAttentePaiement, sans événement ni garde (la création par Creer(panier) s'indique en note)
  • EnAttentePaiement → Payee : PaiementAccepte()
  • EnAttentePaiement → Annulee : after(48 h)
  • Payee → Preparee : MarquerPreparee()
  • Payee → Annulee : Annuler() / rembourser
  • Preparee → Annulee : Annuler() / rembourser
  • Preparee → Expediee : Expedier(numeroSuivi)
  • Expediee → Livree : LivraisonConfirmee()
  • Livree → ◉ et Annulee → ◉

On peut regrouper Payée et Préparée dans un état composite « Annulable », d'où part une seule transition Annuler() / rembourser.

2. Le code

csharp
public enum EtatCommande { EnAttentePaiement, Payee, Preparee, Expediee, Livree, Annulee }

public class Commande
{
    public EtatCommande Etat { get; private set; } = EtatCommande.EnAttentePaiement;
    public DateTime DateCreation { get; }
    public string? NumeroSuivi { get; private set; }
    public bool Remboursee { get; private set; }

    public Commande(DateTime dateCreation)
    {
        DateCreation = dateCreation;
    }

    public void PaiementAccepte() => Passer(EtatCommande.EnAttentePaiement, EtatCommande.Payee);

    public void AnnulerSiImpayee(DateTime maintenant)
    {
        if (Etat == EtatCommande.EnAttentePaiement && maintenant - DateCreation >= TimeSpan.FromHours(48))
        {
            Etat = EtatCommande.Annulee;
        }
    }

    public void MarquerPreparee() => Passer(EtatCommande.Payee, EtatCommande.Preparee);

    public void Expedier(string numeroSuivi)
    {
        Passer(EtatCommande.Preparee, EtatCommande.Expediee);
        NumeroSuivi = numeroSuivi;
    }

    public void LivraisonConfirmee() => Passer(EtatCommande.Expediee, EtatCommande.Livree);

    public void Annuler()
    {
        if (Etat != EtatCommande.Payee && Etat != EtatCommande.Preparee)
            throw new InvalidOperationException($"Impossible d'annuler une commande à l'état {Etat}.");
        Remboursee = true;                 // effet : rembourser
        Etat = EtatCommande.Annulee;
    }

    private void Passer(EtatCommande depart, EtatCommande arrivee)
    {
        if (Etat != depart)
            throw new InvalidOperationException($"Transition impossible depuis l'état {Etat}.");
        Etat = arrivee;
    }
}

L'événement temporel after(48 h) devient une méthode AnnulerSiImpayee, appelée régulièrement par une tâche planifiée : une classe métier ne « sent » pas le temps passer. Recevoir l'heure en paramètre la rend testable.

3. Les tests

  • une commande neuve est en attente de paiement ;
  • PaiementAccepte() fait passer d'en attente à payée ;
  • AnnulerSiImpayee à 47 h 59 ne change rien, à 48 h annule ;
  • AnnulerSiImpayee sur une commande payée ne change rien ;
  • la suite MarquerPreparee(), Expedier("…"), LivraisonConfirmee() mène à livrée, avec le numéro de suivi ;
  • Annuler() sur une commande payée, puis sur une préparée : annulée et remboursée ;
  • Annuler() sur une commande expédiée lève une exception et ne rembourse pas ;
  • Expedier() sur une commande seulement payée (pas préparée) lève une exception.

Chaque transition dessinée a son test, et les transitions interdites les plus dangereuses (annuler une commande expédiée, expédier sans préparer) aussi.

Tu avais trouvé ?

Cas 6. Mutuelle : étude de cas de type E7

Exercice 6 sur 6 Cas 6 : l'espace adhérent de la mutuelle Réussi À revoir

Une mutuelle santé ouvre un espace adhérent. Le dossier documentaire fournit l'extrait suivant du diagramme des classes métier et de leur code (C#).

  • Adherent : - numero : string, - nom : string, - email : string, - hashMotDePasse : string ; + GetLesRemboursements() : List<Remboursement>, + GetLesTentatives() : List<TentativeConnexion>, + VerifierMotDePasse(mdp : string) : bool ;

  • Remboursement : - dateSoin : DateOnly, - acte : string, - montantPaye : decimal, - montantRembourse : decimal ; + GetDateSoin() : DateOnly, + GetMontantRembourse() : decimal ;

  • TentativeConnexion : - dateHeure : DateTime, - reussie : bool, - adresseIp : string ; + GetDateHeure() : DateTime, + EstReussie() : bool ;

  • Adherent "1" ◆──── "0..*" Remboursement ; Adherent "1" ◆──── "0..*" TentativeConnexion.

  1. Explique la relation entre Adherent et TentativeConnexion. Pourquoi le mot de passe est-il stocké sous forme de hashMotDePasse ?
  2. Écris la méthode TotalRembourse(annee : int) : decimal de la classe Adherent.
  3. Pour limiter les attaques par force brute, la mutuelle bloque l'accès d'un adhérent pendant 15 minutes après 5 tentatives échouées consécutives. Écris la méthode EstBloque(maintenant : DateTime) : bool d'Adherent, qui renvoie vrai si les 5 dernières tentatives ont échoué et que la dernière date de moins de 15 minutes. La liste des tentatives est dans l'ordre chronologique.
  4. Pourquoi la classe TentativeConnexion mémorise-t-elle l'adresse IP ? Quelle obligation pèse sur ces données ?
Voir la réponseMasquer la réponse de l'exercice 6

1. Une composition : un adhérent a zéro ou plusieurs tentatives de connexion ; chaque tentative concerne un seul adhérent et n'existe pas sans lui (elle est supprimée avec son compte). Le mot de passe est haché (avec un algorithme lent et salé comme bcrypt ou Argon2) : s'il y a une fuite de la base, l'attaquant n'obtient pas les mots de passe en clair, et l'application vérifie un mot de passe en hachant la saisie et en comparant les empreintes, par VerifierMotDePasse (hachage des mots de passe).

2.

csharp
public decimal TotalRembourse(int annee)
{
    decimal total = 0;
    foreach (Remboursement r in GetLesRemboursements())
    {
        if (r.GetDateSoin().Year == annee)
        {
            total += r.GetMontantRembourse();
        }
    }
    return total;
}

3.

csharp
public bool EstBloque(DateTime maintenant)
{
    List<TentativeConnexion> tentatives = GetLesTentatives();
    if (tentatives.Count < 5) return false;

    // les 5 dernières tentatives doivent toutes avoir échoué
    for (int i = tentatives.Count - 5; i < tentatives.Count; i++)
    {
        if (tentatives[i].EstReussie()) return false;
    }

    // la dernière doit dater de moins de 15 minutes
    DateTime derniere = tentatives[tentatives.Count - 1].GetDateHeure();
    return maintenant - derniere < TimeSpan.FromMinutes(15);
}

Trois points qui rapportent : le test Count < 5 évite un indice négatif ; la boucle ne regarde que les cinq dernières tentatives (« consécutives ») ; la comparaison de durée utilise la dernière tentative. Avec cinq échecs à 10 h 00, 10 h 01, 10 h 02, 10 h 03 et 10 h 04, l'adhérent est bloqué à 10 h 10 (6 minutes après le dernier échec) et débloqué à 10 h 19 (15 minutes après).

4. L'adresse IP sert à détecter et à tracer les attaques : beaucoup d'échecs depuis la même adresse sur des comptes différents signalent une attaque par dictionnaire ou par bourrage d'identifiants, et permettent de bloquer l'adresse. Mais c'est une donnée personnelle au sens du RGPD : sa collecte doit avoir une finalité déclarée (la sécurité), une durée de conservation limitée (on ne garde pas ces traces indéfiniment) et un accès restreint (cours RGPD).

Tu avais trouvé ?

Chapitre terminé !

Fais les 6 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Aide-mémoire UML : notations et symbolesRevoir le sommaire du cours