Chapitre 914 minIntermédiaire
Diagramme d'états-transitions : la machine à états d'un objet
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- décrire le cycle de vie d'un objet par ses états et ses transitions ;
- écrire une transition complète : événement, garde et effet ;
- traduire une machine à états en code C# ;
- déduire les tests unitaires d'un diagramme d'états-transitions.
4 exercices corrigés1 schéma
Le diagramme d'états-transitions (ou machine à états, state machine diagram) décrit le cycle de vie d'un objet : les états par lesquels il passe, et les transitions qui le font changer d'état, déclenchées par des événements, sous certaines conditions, avec certains effets. On le dessine pour les objets dont le comportement dépend de leur état : une commande, un ticket, une réservation, une fiche de frais. Ce chapitre présente la notation, la syntaxe d'une transition, la traduction en code et les vérifications qui en découlent.
États et transitions
Un état est une situation durable dans la vie de l'objet, pendant laquelle il réagit d'une certaine façon aux événements. Il se dessine par un rectangle aux coins arrondis, nommé par un adjectif ou un participe : « Créée », « Clôturée », « Validée ». Une fiche clôturée accepte d'être validée ; une fiche créée, non.
Une transition est le passage d'un état à un autre. Elle se dessine par une flèche et porte un libellé de la forme :
événement [garde] / effet
- l'événement (trigger) déclenche la transition : l'appel d'une opération (
Valider(comptable)), un signal reçu (virement confirmé), un événement temporel (when(1er du mois),after(30 jours)) ; - la garde est une condition, entre crochets : la transition n'a lieu que si elle est vraie au moment de l'événement ;
- l'effet est l'action exécutée pendant la transition, après la barre oblique.
Les trois parties sont facultatives. Une transition sans événement est franchie dès que l'action de l'état est terminée.
| Élément | Notation |
|---|---|
| État initial | Un disque plein. La transition qui en part ne porte ni événement ni garde : on indique en note l'opération qui crée l'objet (ici Visiteur.OuvrirFiche(mois)) |
| État final | Un disque plein entouré d'un cercle ; l'objet a fini sa vie (il peut être détruit) |
| État | Un rectangle arrondi, avec son nom |
| Transition | Une flèche, avec événement [garde] / effet |
| Transition réflexive | Une flèche qui revient sur le même état : l'objet sort puis rentre |
| Activité d'entrée | entry / action dans l'état : exécutée à chaque fois qu'on y entre |
| Activité de sortie | exit / action : exécutée à chaque fois qu'on en sort |
| Activité continue | do / activité : exécutée tant qu'on reste dans l'état |
| Transition interne | événement / action écrit dans l'état : on traite l'événement sans sortir de l'état |
| État composite | Un état qui contient lui-même des sous-états |
| Point de choix | Un losange : la transition se poursuit vers une branche ou une autre selon des gardes |
Le cycle de vie d'une fiche de frais
La fiche de frais d'AppliFrais passe par cinq états, que la base de données code sur deux lettres (CR, CL, VA, MP, RB) et que le code C# représente par l'énumération EtatFiche.
Ce que le diagramme dit, et que le code doit respecter :
- une fiche naît à l'état Créée quand le visiteur l'ouvre ; tant qu'elle y est, il peut saisir et modifier ses frais (transitions internes : la fiche ne change pas d'état) ;
- quand il a terminé, le visiteur appelle
Cloturer(); le 1er du mois suivant, le planificateur le fait pour les fiches restées ouvertes. La fiche passe à l'état Clôturée, et on ne peut plus ajouter de frais ; - dans cet état, le comptable peut refuser une ligne hors forfait (transition interne), puis il la valide : elle passe à Validée, et son montant validé est calculé pendant la transition ; en entrant dans cet état, le visiteur est notifié (le code du chapitre 6 laisse cette notification à un service de messagerie, qu'il ne montre pas) ;
- elle est mise en paiement, puis remboursée quand le service de paie confirme le virement ;
- aucune flèche ne revient en arrière : une fiche validée ne peut pas être rouverte. C'est une règle de gestion, que le diagramme rend évidente ;
- au bout de dix ans, durée de conservation des pièces comptables, la fiche est archivée et sort de l'application : c'est l'état final.
Les états composites
Un état composite regroupe plusieurs sous-états qui partagent un comportement. Clôturée, Validée et Mise en paiement ont un point commun : la fiche est verrouillée, le visiteur ne peut plus rien modifier. On pourrait les regrouper dans un état composite « Verrouillée ». Une transition qui part du contour de l'état composite s'applique à tous ses sous-états : par exemple, une transition Annuler() [fraude constatée] vers un état « Annulée » partant du contour de « Verrouillée » s'appliquerait que la fiche soit clôturée, validée ou mise en paiement.
Les états composites évitent de multiplier les flèches identiques. Au BTS, il suffit de savoir les lire.
De la machine à états au code
Une machine à états se traduit de deux façons.
Avec une énumération et des gardes dans chaque opération. C'est la méthode d'AppliFrais (chapitre 6) : l'état est un attribut de type EtatFiche, et chaque opération qui déclenche une transition vérifie d'abord l'état de départ, puis fait l'effet, puis change l'état.
public void Valider(Comptable comptable)
{
VerifierEtat(EtatFiche.Cloturee); // la transition ne part que de Clôturée
MontantValide = Total(); // l'effet : / montantValide := Total()
ValideePar = comptable;
Etat = EtatFiche.Validee; // l'état d'arrivée
}
Une opération appelée dans un état d'où ne part aucune transition correspondante lève une exception : c'est la traduction de « tout ce qui n'est pas dessiné est interdit ».
Avec une table de transitions. Quand les transitions sont nombreuses, on les range dans une table (état de départ, événement) → état d'arrivée, que le code consulte. C'est le principe des bibliothèques de machines à états. Pour une machine de cinq ou six états, la première méthode est plus simple et plus lisible.
Le patron de conception État (State) va plus loin : une classe par état, chacune implémentant les opérations à sa façon. Il est utile quand le comportement de chaque état est riche, rarement au BTS.
Écrire les tests à partir du diagramme
Le diagramme d'états-transitions donne directement la liste des tests unitaires à écrire :
- un test par transition dessinée : à partir de l'état de départ, l'opération mène à l'état d'arrivée et produit son effet (une fiche clôturée validée par Inès passe à Validée, avec le bon montant) ;
- un test par transition interdite importante : une fiche créée qu'on tente de valider lève une exception et reste Créée ; une fiche validée qu'on tente de clôturer aussi ;
- un test par garde : la garde vraie, puis la garde fausse.
Avec cinq états, quatre transitions entre eux et trois transitions internes, une douzaine de tests couvrent tout le cycle de vie. Le cours Tests montre comment les écrire (écrire un bon test unitaire).
Exercices
Exercice 1 sur 4 Lire une transition Réussi À revoir
Explique en français chacune de ces transitions d'un diagramme d'états-transitions de la classe Reservation (location de vélos) :
Confirmee → EnCours : RetirerVelo() [pièce d'identité vérifiée] / démarrer le chronomètreEnAttentePaiement → Annulee : after(15 min)EnCours → Terminee : RendreVelo() / calculer le prix final- Dans l'état
EnCours:Prolonger(minutes) / recalculer l'heure de fin
Voir la réponseMasquer la réponse de l'exercice 1
- Une réservation confirmée passe à « en cours » quand le client retire le vélo, à condition que sa pièce d'identité ait été vérifiée ; pendant le passage, le chronomètre démarre. Sans vérification, l'événement est ignoré : la réservation reste confirmée.
- Une réservation en attente de paiement est annulée automatiquement si elle y reste 15 minutes : événement temporel, sans garde ni effet.
- Quand le client rend le vélo, la réservation passe à « terminée », et le prix final est calculé pendant la transition.
- C'est une transition interne : la prolongation recalcule l'heure de fin sans quitter l'état « en cours » (les éventuelles activités
entryetexitne sont pas rejouées).
Tu avais trouvé ?
Exercice 2 sur 4 Dessiner la machine à états d'un ticket Réussi À revoir
Un ticket d'assistance est ouvert à sa création. Quand un technicien le prend en charge, il est en cours. Le technicien peut le mettre en attente d'une réponse de l'utilisateur ; la réponse le remet en cours. Quand le technicien a résolu l'incident, le ticket est résolu et l'utilisateur en est averti. L'utilisateur peut alors confirmer (le ticket est clos) ou rouvrir le ticket (il revient en cours). Sans réponse de l'utilisateur sous 7 jours, un ticket résolu est clos automatiquement. Un ticket clos n'évolue plus.
Donne les états et les transitions, au format Départ → Arrivée : événement [garde] / effet.
Voir la réponseMasquer la réponse de l'exercice 2
États : Ouvert, EnCours, EnAttente, Resolu, Clos.
Transitions
● → Ouvert, sans événement ni garde : l'opération qui crée le ticket,Creer(titre, description), s'indique dans une noteOuvert → EnCours : PrendreEnCharge(technicien)EnCours → EnAttente : DemanderInformation(question)EnAttente → EnCours : ReponseUtilisateur(texte)EnCours → Resolu : Resoudre(solution) / avertir l'utilisateur(ouentry / avertir l'utilisateurdans l'état Resolu)Resolu → Clos : Confirmer()Resolu → EnCours : Rouvrir(motif)Resolu → Clos : after(7 jours)Clos → ◉(état final), ou pas de transition sortante : « un ticket clos n'évolue plus ».
Deux transitions mènent de Resolu à Clos, avec deux événements différents : c'est permis. Pas de flèche de Clos vers ailleurs : un ticket clos ne se rouvre pas, il faudrait en ouvrir un nouveau.
Tu avais trouvé ?
Exercice 3 sur 4 Du diagramme au code Réussi À revoir
Écris en C# les opérations PrendreEnCharge(Technicien technicien), Rouvrir(string motif) et Resoudre(string solution) de la classe Ticket de l'exercice précédent. L'état est une énumération EtatTicket ; le ticket mémorise son technicien, sa solution et le nombre de réouvertures.
Voir la réponseMasquer la réponse de l'exercice 3
public enum EtatTicket { Ouvert, EnCours, EnAttente, Resolu, Clos }
public class Ticket
{
public EtatTicket Etat { get; private set; } = EtatTicket.Ouvert;
public Technicien? Technicien { get; private set; }
public string? Solution { get; private set; }
public int NbReouvertures { get; private set; }
public void PrendreEnCharge(Technicien technicien)
{
VerifierEtat(EtatTicket.Ouvert);
Technicien = technicien;
Etat = EtatTicket.EnCours;
}
public void Resoudre(string solution)
{
VerifierEtat(EtatTicket.EnCours);
Solution = solution;
Etat = EtatTicket.Resolu;
// effet : avertir l'utilisateur (par un service de messagerie)
}
public void Rouvrir(string motif)
{
VerifierEtat(EtatTicket.Resolu);
NbReouvertures++;
Solution = null;
Etat = EtatTicket.EnCours;
}
private void VerifierEtat(EtatTicket attendu)
{
if (Etat != attendu)
throw new InvalidOperationException($"Ticket à l'état {Etat}, {attendu} attendu.");
}
}
Chaque opération suit le même schéma : vérifier l'état de départ, faire l'effet, changer d'état. Appeler Rouvrir sur un ticket clos lève une exception, puisqu'aucune transition ne part de Clos. Pour être complet, il faudrait aussi enregistrer le motif de réouverture, par exemple dans un historique.
Tu avais trouvé ?
Exercice 4 sur 4 Trouver les incohérences Réussi À revoir
Le diagramme de classes de Reservation montre les opérations Confirmer(), Annuler() et RendreVelo(). Son diagramme d'états-transitions montre les transitions EnAttentePaiement → Confirmee : PaiementAccepte(), Confirmee → EnCours : RetirerVelo(), EnCours → Terminee : RendreVelo() et Confirmee → Annulee : Annuler(). Relève les incohérences entre les deux diagrammes.
Voir la réponseMasquer la réponse de l'exercice 4
Confirmer()existe dans le diagramme de classes mais ne déclenche aucune transition. Soit elle est inutile, soit c'est elle qui devrait déclencherEnAttentePaiement → Confirmeeà la place de l'événementPaiementAccepte().PaiementAccepte()etRetirerVelo()déclenchent des transitions, mais n'apparaissent pas dans le diagramme de classes : il faut les y ajouter (ou dire qu'il s'agit de signaux reçus d'un autre système, pour le paiement).Annuler()n'est possible que depuis Confirmée : une réservation en attente de paiement ne peut pas être annulée par le client. C'est peut-être voulu (elle s'annule toute seule au bout de 15 minutes), mais il faut le vérifier avec le client.- Il n'y a ni état initial ni état final dessinés : on ne sait pas dans quel état naît une réservation.
Tu avais trouvé ?