9  Réalisations de cas d’utilisation (RDCU)

Les réalisations de cas d’utilisation (RDCU) sont le sujet du chapitre F17/A18 . Voici les points importants pour la méthodologie :

Tout le processus de proposer une solution (RDCU) peut être visualisé comme un diagramme d’activités, comme dans la figure 9.1.

Note: Une solution détaillée est faite pour chaque opération système.Donc, il faut utiliser le DSS, les contrats d'opération, le MDD et lesprincipes GRASP pour ce travail.ex.créerNouvelleVente()Revoir l'opération systèmeAppliquerGRASP Contrôleur(Chapitre F16.13/A17.13)Déterminer le contrôleurLespostconditions, ex.,-Une instancevde Vente a été créée-va été associée au Registre-Des attributs devont été initialisésRappeler le contrat d'opérationAppliquerGRASP Créateur,GRASP Expert,ID en objets,etc. Chapitre F16.8/A17.8Concevoir (et raffiner) un diagramme d'interactionpour l'opération système,satisfaisant toutesles postconditionsdu contrat d'opération etretournant l'information selon le message deretourdu DSS, le cas échéantreste des postconditions insatisfaitesou infos pas encore retournéesreste des opérations système

Figure 9.1: Aide-mémoire pour faire une RDCU. L’étape en rouge nécessite beaucoup de pratique, selon la complexité des postconditions. Vous pouvez vous attendre à ne pas la réussir du premier coup. (PlantUML)

9.1 Spécifier le contrôleur

Pour commencer une RDCU, on spécifie le contrôleur selon GRASP. Dans les travaux réalisés selon la méthodologie de ce manuel, vous devez indiquer pourquoi vous avez choisi telle classe pour être le contrôleur. Ce n’est pas un choix arbitraire. Référez-vous à la définition dans le tableau 6.1.

Pour initialiser les liens entre la couche présentation et les contrôleurs GRASP, Larman vous propose de le faire dans la RDCU pour l’initialisation, le scénario Démarrer.

9.2 Satisfaire les postconditions

9.2.1 Créer une instance

Certaines postconditions concernent la création d’une instance. Dans votre RDCU, vous devez respecter le GRASP Créateur. Référez-vous à la définition dans le .

Mise en garde

Une erreur potentielle est de donner la responsabilité de créer à un contrôleur, puisqu’il a les données pour initialiser l’objet. Bien que ce soit justifiable par le principe GRASP Créateur, il vaut mieux favoriser une classe qui agrège l’objet à créer, le cas échéant.

9.2.2 Former une association

Pour les postconditions où il faut former une association entre un objet a et b, il y a plusieurs façons de faire.

  • S’il y a une agrégation entre les objets, il s’agit probablement d’une méthode add() sur l’objet qui agrège.
  • S’il y a une association simple, il faut considérer la navigabilité de l’association. Est-ce qu’il faut pouvoir retrouver l’objet a à partir de l’objet b, ou vice-versa ? Il s’agira d’une méthode setB(b) sur l’objet a (pour trouver b à partir de a), etc.
  • S’il faut former une association entre un objet et un autre « sur une base de correspondance avec » un identifiant passé comme argument, alors il faut repérer le bon objet d’abord. Voir la section Transformer identifiants en objets.

Dans la plupart des cas, la justification GRASP pour former une association est Expert, défini dans le . Il faut faire attention à la visibilité .

9.2.3 Modifier un attribut

Pour les postconditions où il faut modifier un attribut, c’est assez évident. Il suffit de suivre le principe GRASP Expert, défini dans le . Très souvent, c’est une méthode setX(valeur)X correspond à l’attribut qui sera modifié à valeur. Attention à la visibilité .

Lorsque l’attribut d’un objet doit être modifié juste après la création de ce dernier, ça peut se faire dans le constructeur, comme on voit dans la figure 9.2.

:Plateauloopcreate(nom):Case

Figure 9.2: Combiner la création d’une instance et une modification de son attribut dans un constructeur. (PlantUML)

9.3 Visibilité

Dans tous les cas, si un message est envoyé à un objet, ce dernier doit être visible à l’objet qui lui envoie le message. Régler les problèmes de visibilité nécessite de la créativité. Il est difficile d’enseigner cette démarche, mais les points suivants peuvent aider :

  • Pour un objet racine (par exemple Université), il peut s’agir d’un objet Singleton, qui aura une visibilité globale, c’est-à-dire que n’importe quel objet pourrait lui envoyer un message. Cependant, les objets Singleton posent des problèmes de conception, notamment pour les tests. Il vaut mieux éviter ce choix, si possible.
    Voir cette réponse sur Stack Overflow .
  • Sinon, il faudra que l’objet émetteur ait une référence de l’objet récepteur. Par exemple, dans la figure 9.3, la référence à b peut être :
    • stockée comme un attribut de a,
    • passée comme un argument dans un message antérieur, ou
    • affectée dans une variable locale de la méthode où unMessage() sera envoyé.

Pour plus de détails, voir le chapitre sur la Visibilité (F18/A19) .

a:Ab:B...unMessage()...

Figure 9.3: L’objet b doit être visible à l’objet a si a veut lui envoyer un message. (PlantUML)

Pour initialiser les références nécessaires pour la bonne visibilité, Larman vous propose de faire ça dans la RDCU pour l’initialisation, le scénario Démarrer.

9.4 Transformer identifiants en objets

La directive d’utiliser les types primitifs pour les opérations système nous mène à un problème récurrent dans les RDCU : transformer un identifiant (souvent de type String ou int) en objet. Larman vous propose un idiome (pas vraiment un patron) nommé Transformer identifiant en objet qui sert à repérer la référence d’un objet qui correspond à l’identifiant.

Il y a un exemple à la figure 9.4 provenant du chapitre sur l’Application des patterns GoF (Figure 23.18) . Un autre exemple du livre de Larman (2005) est l’identifiant codeArticle transformé en objet DescriptionProduit par la méthode
CatalogueProduits.getDescProduit(codeArticle:String):DescriptionProduit.

:Registre:Magasinv:VentesaisirClientPourRemise(idClient)c = getClient(idClient)Selon Expert etles ID en objetssaisirClientPourRemise(c:Client)

Figure 9.4: Un identifiant idClient:String est transformé en objet c:Client, qui est ensuite envoyé à la Vente en cours. (PlantUML)

La Section 9.5 explique comment implémenter la transformation avec un tableau associatif.

9.5 Utilisation d’un tableau associatif (Map<clé, objet>)

Pour transformer un ID en objets, il est pratique d’utiliser un tableau associatif (aussi appelé dictionnaire ou map en anglais) . L’exemple du livre de Larman (2005) concerne le problème de repérer une Case Monopoly à partir de son nom (String). C’est la figure A17.7/F17.7 .

Notez que les exemples de Larman (2005) ne montrent qu’un seul type dans le tableau associatif, par exemple Map<Case>, tandis que, normalement, il faut spécifier aussi le type de la clé, par exemple Map<String, Case>.

Un tableau associatif fournit une méthode get ou find pour rechercher un objet à partir de sa clé (son identifiant). La figure 9.5 en est un exemple.

:PlateaucMap:Map<String,Case>s = getCase(nom)Plateau agrège toutes lesCases et possède un Mapavec nom comme clé.c = get(nom) : Case

Figure 9.5: Exemple de l’utilisation d’un tableau associatif pour trouver une Case Monopoly à partir de son nom. (PlantUML)

Dans la section suivante, l’initialisation des éléments utilisés dans les RDCU (comme des tableaux associatifs) est expliquée.

9.6 RDCU pour l’initialisation, le scénario Démarrer

Le lancement de l’application correspond à la RDCU « Démarrer ». La section Initialisation et cas d’utilisation Démarrer (F17.4, p.345) ou Initialization and the ‹Start Up› Use Case (A18.4, p.274)  traite ce sujet important. C’est dans cette conception où il faut mettre en place tous les éléments importants pour les hypothèses faites dans les autres RDCU, par exemple les classes de collection (Map), les références pour la visibilité, l’initialisation des contrôleurs, etc.

Voici quelques points importants :

  • Le lancement d’une application dépend du langage de programmation et du système d’exploitation.
  • À chaque nouvelle RDCU, on doit possiblement actualiser la RDCU « Démarrer » pour tenir compte des hypothèses faites dans la dernière RDCU. Elle est assez « instable » pour cette raison. Larman recommande de faire sa conception en dernier lieu.
  • Il faut choisir l’objet du domaine initial, qui est souvent l’objet racine, mais ça dépend du domaine. Cet objet aura la responsabilité, lors de sa création, de générer ses « enfants » directs, puis chaque « enfant » aura à faire la même chose selon la structure. Par exemple, selon le MDD pour le jeu Risk à la figure 4.1, JeuRisk pourrait être l’objet racine, qui devra créer l’objet PlateauRisk et les cinq instances de . L’objet PlateauRisk, lors de son initialisation, pourra instancier les 42 objets Pays et les six objets Continent, en passant à chaque Continent ses objets Pays lors de son initialisation. Si PlateauRisk fournit une méthode getPays(nom) qui dépend d’un tableau associatif selon Transformer identifiants en objets, alors c’est dans l’initialisation de cette classe que l’instance de Map<String,Pays> sera créée.
  • Selon l’application, les objets peuvent être chargés en mémoire à partir d’un système de persistance, par exemple une base de données ou un fichier. Pour l’exemple de Risk, PlateauRisk pourrait charger, à partir d’un fichier JSON, des données pour initialiser toutes les instances de Pays. Pour une application d’inscription de cours à l’université, il se peut que toutes les descriptions de cours soient chargées en mémoire à partir d’une base de données. Une base de données amène un lot d’avantages et d’inconvénients, et elle n’est pas toujours nécessaire. Dans la méthodologie de ce manuel, on n’aborde pas le problème des bases de données (c’est le sujet d’un autre cours).

:ObjetMaincreate:JeuRiskJeuRisk est l'objet racine.Contrôleurne s'applique pas ici, car il ne s'agit pas d'une opération systèmeloop[i<5]dés[i] = create:DéparCréateurJeuRisk agrège Décreate:PlateauRiskparCréateurJeuRisk agrège PlateauRiskpMap = createpMap:Map<String,Pays>parCréateur: PlateauRisk agrège Map<String,Pays>continentsAvecPays[] =chargerContinentsAvecPaysParExpertCharger les données d'un fichier JSONloop[i<continentsAvecPays.size]create(continentsAvecPays[i].nom, ...):ContinentparCréateur: PlateauRisk agrège Continentloop[j<continentsAvecPays[i].pays.size]p = create(continentsAvecPays[i].pays[j].nom, ...)p:PaysparCréateur: PlateauRisk agrège Paysadd(p)ParExpertadd(p)ParExpert

Figure 9.6: Exemple de l’initialisation partielle du jeu Risk. (PlantUML)

9.7 Réduire le décalage des représentations

Le principe du Décalage des représentations est la différence entre la modélisation (la représentation) du problème (du domaine) et la modélisation de la solution. Lorsqu’on fait l’ébauche d’une RDCU, on peut réduire le décalage des représentations principalement en s’inspirant des classes conceptuelles (du modèle du domaine) pour proposer des classes logicielles dans la solution décrite dans la RDCU. Plus une solution ressemble à la description du problème, plus elle sera facile à comprendre.

Mise en garde

Une application de patterns GoF à la solution peut nuire à ce principe, car ces patterns ajoutent souvent des classes logicielles n’ayant aucun lien avec le modèle du domaine. Par exemple, un Visiteur ou un Itérateur sont des classes logicielles sans binôme dans le modèle du domaine. Il faut vérifier avec une personne expérimentée (l’architecte du projet si possible) que l’application du pattern est justifiée, qu’elle apporte de vrais bénéfices au design en dépit des désavantages dus à des classes ajoutées. Chaque fois qu’on propose des classes logicielles qui n’ont pas de liens avec la représentation du problème du domaine, on augmente le décalage des représentations et on rend la solution un peu plus difficile à comprendre. C’est aussi une forme de Complexité circonstancielle (provenant des choix de conception). Ce dilemme est un bon exemple de la nature pernicieuse de la conception de logiciels. Il est très difficile, même pour les experts en conception, de trouver un bon équilibre entre toutes les forces : la maintenabilité, la simplicité, les fonctionnalités, etc. Vous pouvez en lire plus dans cette réponse sur StackOverflow .

9.8 Pattern « Faire soi-même »

Dans la section F30.8/A33.7 , Larman mentionne le pattern « Faire soi-même » de Peter Coad (1997) qui permet de réduire le Décalage des représentations, même s’il ne représente pas exactement la réalité des objets (voir la figure 9.7 (a) ):

(a) Dés dans la vraie vie (« Hand of chance » (CC BY 2.0) par Alexandra E Rust).

face : intbrasser()

(b) Dé dans un logiciel selon Faire soi-même.

Figure 9.7: Faire soi-même : « Moi, objet logiciel, je fais moi-même ce qu’on fait normalement à l’objet réel dont je suis une abstraction » de Coad (1997).

9.9 Exercices

Exercice 9.1 (RDCU pour le cas d’utilisation Ouvrir la caisse) Faites les RDCU pour le cas d’utilisation Ouvrir la caisse. Vous y trouverez également des artefacts tels que le DSS, les contrats d’opération et le modèle du domaine. Ils sont essentiels pour faire les RDCU selon la méthodologie.

Exercice 9.2 (Coder des méthodes à partir des diagrammes de séquence) Pour chacun des diagrammes suivants, écrire les classes TypeScript avec les méthodes indiquées dans le diagramme. (Cet exercice complémente le livre de Larman, 2005 à la section F18.6/A20.4.)

Astuce

Vous pouvez utiliser VSCode pour vous aider avec le TypeScript, mais cet outil n’est pas forcément permis lors d’un examen.

Voici un modèle à suivre. Pour le diagramme suivant :

:A:Bexecute(3)result = setItem("Fred")

Figure 9.8: Exemple de diagramme de séquence.

On code les classes suivantes en TypeScript :

class A {
    b: B;  // A envoie un message à B, visibilité d'attribut
    execute(arg0:number):any {
        const result = this.b.setItem("Fred");
    }
}

class B {
    setItem(arg0:string):any {
        //...
    }
}
  1. Écrire le code pour la figure suivante.

    :Bernard:Aliceinitallô(12)create:Autre"oui"15

  1. Écrire le code pour la figure suivante décrivant la création de la collection de Vente (tirée de Larman, 2005, figure. 17.6).

    :Registreselon Créateur etContrôleurcréerNouvelleVenteRegistre crée uneVente selon Créateurcreate:Venteselon Créateur, Vente crée unecollection vide (par exemple uneList) destinée a mémoriser lesinstances de LigneArticlesl'activation de Vente est implicitement dansle constructeur de l'instance de VentecreatelignesArticles :List<LigneArticles>

  1. Écrire le code pour la figure suivante décrivant l’utilisation d’un Cornet dans le jeu de Monopoly (tirée de Larman, 2005, fig. F22.9)

    j : Joueur: Cornet: Plateaupos : CaseSelon Fabrication pureprendreTourlancervalTot = getTotalpos = getCase( pos, valTot )atterrirSur( j )

  1. Écrire le code pour les figures suivantes décrivant les appels polymorphes de la méthode atterrirSur dans le jeu de Monopoly (tirées de Larman, 2005, fig. F22.6 et F22.7)

    : CaseImpôtsj : JoueuratterrirSur(j)selon Polymorphismec = getCashselon ExpertdiminuerCash(min(200, 10% de c))selon Expert

    : CaseAllezEnPrisonj : JoueuratterrirSur(j)selon PolymorphismesetPosition(prison)selon Expert