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 proposition d’une solution (RDCU) peut être visualisé comme un diagramme d’activités, comme illustré sur 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, p. 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.8).Concevoir (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 des 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, selon la définition dans le tableau 6.1.

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, si nécessaire.

9.2.2 Former une association

Pour les postconditions où il faut former une association entre un objet a et un objet 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 » ou « correspondant à » un identifiant passé comme argument (voir les postconditions de l’exemple à la section Section 8.3), alors il faut repérer le bon objet d’abord. Voir la section Transformer identifiants en objets.

Dans la plupart des cas, la justification pour former une association est GRASP Expert, défini dans le tableau 6.1. 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 tableau 6.1. Très souvent, c’est une méthode setX(valeur), où 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 illustré sur 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 une approche orientée objet, puisqu’on doit éviter trop de couplage, un objet ne voit pas tous les autres objets. Si un objet a veut envoyer un message à un objet b, ce dernier doit lui être visible. Ça veut dire que a doit connaître une référence vers b.

Avertissement

Dans un diagramme de séquence pour une RDCU, il est facile de montrer des objets a et b et de dessiner une flèche entre l’objet a et b puis d’écrire le nom d’une méthode pour représenter l’appel. Cependant, dans un programme codé en TypeScript ou en Java, il y aura des erreurs de compilation si a n’a pas une référence pour b. L’approche de Larman propose d’être sensible à la visibilité lors de la conception (en faisant les RDCU), car ajouter une visibilité implique l’augmentation du couplage.

Régler les problèmes de visibilité lorsqu’on crée une RDCU nécessite de la créativité. Il faut pratiquer pour apprendre la 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 (a) ait une référence de l’objet récepteur (b). Par exemple, sur 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 les identifiants en objets

La directive d’utiliser les types primitifs pour les arguments dans les opérations système (voir la Chapitre 5) nous mène à un problème récurrent dans les RDCU : transformer un identifiant (un argument de type String ou int) en objet représenté par cet identifiant. Larman vous propose un idiome (pas vraiment un pattern) nommé Transformer identifiant en objet (fin de la section F23.8, p.451/A26.8, IDs to Objects ), qui sert à repérer l’objet qui correspond à l’identifiant.

Il y a un exemple à la figure 9.4 provenant du chapitre sur l’Application des patterns GoF (figure F23.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 identifiant en objet, 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 illustré sur la figure F16.7/A17.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.2, 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 les 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, avec les principes GRASP et les tableaux associatifs pour faciliter la transformation d’identifiants en objets. (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 l’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 spécialistes 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 Stack Overflow .

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. (PlantUML)

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 (Coder des méthodes à partir des diagrammes de séquence) Pour chacun des diagrammes suivants, écrivez 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 VS Code pour vous aider avec le TypeScript, mais cet outil ne sera pas forcément permis lors d’un examen.

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

Figure 9.8: Exemple de diagramme de séquence. (PlantUML)

Voici un modèle à suivre. Pour le diagramme sur la figure 9.8, 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. Écrivez le code pour la figure suivante.

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

  2. Écrivez le code pour la création de la collection de Vente (Larman, 2005), figure F17.6/A18.6 ].

  3. Écrivez le code pour l’utilisation d’un Cornet (à dés, gobelet dans lequel on agite les dés) dans le jeu Monopoly (Larman, 2005), figure F22.9/A25.9 ].

  4. Écrivez le code pour les appels polymorphes de la méthode atterrirSur dans le jeu Monopoly (Larman, 2005), figures F22.6/A25.6 et F22.7/A25.7 ].

Exercice 9.2 (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 présentée dans ce manuel.

Exercice 9.3 (Critique d’une conception) Dans cet exercice, l’objectif est de vous sensibiliser à la facilité de comprendre une conception à partir d’un problème. Un objectif secondaire est de considérer les choix de conception sur le plan de la cohésion et du couplage. Ici, il s’agit du jeu Monopoly, qui est un exemple proposé par Larman (2005), pour lequel il a également proposé un modèle du domaine et une conception, selon la méthodologie.

Pour cet exercice, nous examinerons une conception orientée objet réelle du jeu Monopoly disponible sur GitHub, soit Emojiopoly. Voici le travail à faire.

  • Considérez les deux artefacts :
    • un modèle du domaine de Monopoly proposé par Larman (2005) (il y a une version en français et une autre en anglais, puisque le code TypeScript est en anglais) ;
    • un modèle d’une solution sous forme de diagramme de classes logicielles, créé à partir du code TypeScript dans le dépôt mentionné ci-dessus).
  • Nous faisons une hypothèse que l’équipe qui a développé Emojiopoly n’a pas commencé avec le MDD de Larman.
  • Comparez ces deux artefacts et faites des remarques sur la conception, surtout par rapport au MDD et au décalage des représentations.
  • Faites des remarques sur la solution concernant la cohésion et le couplage.

Diagramme de classes d’Emojiopoly

Pour visualiser la conception, nous avons généré un diagramme de classes en UML sur la figure 9.9 avec l’outil tplant.

Cardtext: stringaction: "advance" | "back" | "choice" | "payeach" | "collecteach" |"doubletransport" | "earn" | "pay" | "stealmoney" | "stolenmoney" |"streetrepairs" | "gotojail"value?: string | number | number[]MonopolyGamejackpot: numbercurrentTurnIndex: numberconsecutiveDoubles: numberplayers: Player[]currentPlayer: Playerdice: number[]isTurnFinished: booleanunpaidDebts: Debt[]chanceCards: Card[]chestCards: Card[]chanceCardIndex: numberchestCardIndex: numberboard: Square[]playerNames: string[]houseRules: HouseRulesmessageEventHandler: (message: string) => voidaddToJackpot(amount: number): voidrollDice(die1?: number, die2?: number): voidmoveToSquare(newSquare: Square, awardSalary?: boolean): voidsendToJail(): voidmoveBack(numberOfSpaces: number): voidpostBail(): voidtakeChance(): voidtakeChest(): voidhandleCard(card: Card): voidmovePlayer(): voidpayDebts(): voidfinishTurn(): voidHouseRulesPlayerownedProperties: Property[]currentSquare: SquareturnsInJail: numbername: stringmoney: numberPropertybuildingsCount: numberisMortgaged: booleanowner: Playername: stringcolor: "brown" | "lightblue" | "pink" | "orange" | "red" | "yellow" |"green" | "blue" | "white"price: numberrent: number[] | "transport" | "utility"buildPrice: numberSquareoccupants: Player[]squareContents: "gotojail" | Property | "go" | "jail" | "parking" |"chance" | "chest" | "incometax" | "supertax"addOccupant(occupant: Player): voidremoveOccupant(occupant: Player): voidDebtamount: numbergame: MonopolyGamedebtor: Playercreditor: PlayerinitialAmount: numberpayDebt(): voidtoString(): string**1**1*11*11

Figure 9.9: Diagramme de classes logicielles (TypeScript) pour le projet Emojiopoly. (PlantUML)

Modèle du domaine de Monopoly

Puisque la solution d’Emojiopoly est en anglais, vous pouvez regarder le modèle du domaine de Monopoly en français (figure F26.35 ) et en anglais (figure A31.35 ) pour vous aider à comprendre les termes.