Caisse informatisée pour supermarché
Caisse informatisée pour supermarché
SUPERMARCHE
1
Etude de cas
Réalisation d'une caisse informatisée.
Chaque type de produit est référencé dans un catalogue, avec son prix associé. Quand le prix
d'un produit doit être modifié, le manager modifie son prix dans le catalogue, puis sur
l'étagère où il est rangé.
La caisse fera les fonctions habituelles d'une caisse : calcul du sous total, calcul du total,
possibilité de rentrer plusieurs articles ayant un même code, retour d'une marchandise avec le
ticket de caisse. Le paiement se fera en monnaie seulement.
La caisse s'exécute sur un PC. Une douchette permettra de lire les codes à barres. Les
informations peuvent être rentrées au clavier, ou à la souris.
2
1. Diagramme d’acteurs
Regardons maintenant les acteurs qui gravitent autour de notre application.
Référen
Fonction
ce
R1 Modifier le prix d'un produit
R2 Calculer le total d'une vente
R3 Rentrer une quantité par article
R4 Calculer le sous total d'un produit
R5 Retourner une marchandise
R6 Payer l'achat
R7 Editer un ticket de vente
R8 Editer un rapport succinct
R9 Editer un rapport détaillé
R10 Se connecter à la caisse
R11 Se connecter en gérant
R12 Définir les droits des usagers
R13 Entrer un produit au catalogue
R14 Supprimer un produit du catalogue
R15 Enregistrer un produit à la caisse
R16 Initialiser la caisse
3
1. Diagramme de contexte statique
4
R3 Rentrer une quantité par article Effectuer un achat
R15 Enregistrer un produit à la caisse Effectuer un achat
R4 Calculer le sous total d'un produit Effectuer un achat
Les flèches
unidirectionnelles
indiquent un lien
principal, c’est à dire
5
quatrième étape : use case description de haut niveau
Il nous faut maintenant définir les cas d’utilisation. Cette description est une description de
haut niveau qui se fait avant l'analyse. Nous sommes toujours dans la définition du besoin.
Une description de haut niveau d'un use case est une description textuelle qui comprend les
chapitre suivants:
L'analyse
Evénement déclencheur du Use Case: Un client arrive à la caisse avec ses achats
Rôle du Use Case: Le caissier passe tous les articles, fait la note, et
encaisse le paiement.
Les références croisées des exigences: R2, R3, R4, R6, R7, R15
Les pré conditions nécessaires au fonctionnement du use case: La caisse est allumée et
initialisée. Un caissier est connecté à la caisse.
Ici il n'y a qu'un scénario possible. Nous verrons dans l'exemple suivant un use case avec
plusieurs scénarios pour mettre en évidence le diagramme d'activité.
6
4) Le caissier indique la fin de vente quand il 5) Le système calcule et affiche le montant
n'y a plus d'article. total de l'achat.
6) Le caissier annonce au client le montant
total.
7) Le client donne au caissier une somme
d'argent supérieure ou égale à la somme
demandée.
8) Le caissier enregistre la somme donnée 9) Le système calcule la somme à rendre au
par le client. client.
10) Le caissier encaisse la somme remise par 11) Le système enregistre la vente effectuée
le client et rend la monnaie au client.
12) Le système édite le ticket de caisse
13) Le caissier donne le ticket de caisse au
client
14) le client s'en va avec les articles qu'il a
achetés.
Variantes du scénario nominal ( celui qui se déroule quand tout se passe bien ).
Paragraphe 2: il peut y avoir plusieurs articles d'un même produit. Le caissier enregistre le
produit ainsi que le nombre d'articles.
Paragraphe 3: s'il y a plusieurs articles d'un même produit, le système calcule le sous total.
Paragraphe 2: Le code produit peut être invalide. Le système affiche un message d'erreur.
Paragraphe 7: Le client n'a pas la somme d'argent suffisante. La vente est annulée. Remarque:
Ceci est une exception qui termine anormalement le use case.
Paragraphe 8: Le caissier n'a pas la monnaie pour rendre au client. Il va faire la monnaie à la
caisse principale. Remarque: ici nous avons fait apparaître un nouvel acteur: le caissier
principal ( ce sera probablement la même personne que le manager mais un acteur différent ).
Paragraphe 12: le système ne peut pas éditer le ticket de caisse. Un message est affiché au
caissier, et celui-ci change le rouleau de papier.
Paragraphe 14: remarquons que si le client repart sans ses articles, il est probable que le
caissier les lui mettra de côté. Cet événement ne change rien à notre système futur. Ce genre
d'incident ne sera pas pris en considération.
En terme de représentation nous pouvons préférer mettre une colonne par acteur pour bien
voir les responsabilités des acteurs vis à vis du système. Ici cela donnerait:
7
4) Le caissier indique la fin 5) Le système calcule et
de vente quand il n'y a plus affiche le montant total de
d'article. l'achat.
6) Le caissier annonce au
client le montant total.
7) Le client donne au caissier 8) Le caissier enregistre la 9) Le système calcule la
une somme d'argent somme donnée par le client. somme à rendre au client.
supérieure ou égale à la
somme demandée.
10) Le caissier encaisse la 11) Le système enregistre la
somme remise par le client et vente effectuée
rend la monnaie au client.
12) Le système édite le ticket
de caisse
13) Le caissier donne le
ticket de caisse au client
14) le client s'en va avec les
articles qu'il a achetés.
Cette représentation met en évidence que le client n'a pas accès au système informatique.
Contraintes non fonctionnelles ( optionnel ): Le magasin reçoit en moyenne 200 clients par
jour. Chaque client achète en moyenne 10 articles.
8
Note : Ceci représente un commentaire dans tout schéma UML.
comment
Le login est
le loginsiest
accepté le
accepté
salarié estsiconnu
le
etsalarié
si son est
motconnu,
de
et son mot de
9
septième étape : diagramme de classe d’analyse
cai
0
catal
u
1 1
0
ca
se fait à
1
Paie 1 eff
payé 0
1 1
v
gé 1 1
con
ti comp
1
1
ligne de
est
1 0
ar
10
Voici notre diagramme de classe enrichi des attributs d'analyse.
cai
0
catal
ut
1 1
0
ca
se fait à
1
Paie
effe
somme 1 0
payé 1
1 v
date :
heure :
gé 1 con
1
ti
1 comp
1
ligne de
quantité :
0
le prix
somme est
0
se 1
rapport description
monn
description
prix
code :
11
neuvième étape : les contrats d'opération
Les Pré conditions décrivent l'état du système, c'est-à-dire l'état des objets du système comme
décrit dans le diagramme des classes d'analyse.
Nous jouons l'opération sur le système, en aveugle, et jusqu'à sa fin.
Notons les changements de l'état du système ( des objets et de leurs associations ) et nous
obtenons les post conditions. Ces post conditions sont décrites sous la forme "has been", c'est-
à-dire l'objet X a été modifié, son attribut Y a été mis à vrai.
Nous allons faire les contrats d'opérations des opérations du use case effectuer un achat. Pour
cela il faut partir du diagramme de séquence boîte noire du use case effectuer un achat et du
diagramme de classe d'analyse, et pour chaque flèche dirigée vers le système, rédiger un
contrat d’opération. Par exemple :
12
Enregistrer un article.
Finir la vente.
Payer la vente.
13
● Exigences: R7 pour le use case effectuer un achat.
● Notes: Si la somme n'est pas suffisante un message est envoyé au
caissier.
● Pré conditions: Il y a une vente v en cours marquée à terminé.
● Post conditions: Un objet de type Paiement p a été créé.
P a été associé à la vente v.
V. total a été affecté à [Link].
La monnaie à rendre ( somme – [Link] ) a été affichée.
Un ticket t a été créé.
La vente v a été associée au ticket t.
Le ticket de vente a été imprimé.
Nous allons voir une autre manière de représenter ces contrats d'opération à l'aide de
diagramme de séquence boîte blanche. Cette manière de représenter les contrats d'opération
est beaucoup plus utilisée que la manière textuelle. Elle n'est pas forcément mieux d'ailleurs…
Maintenant nous allons faire les diagrammes de séquence boîte blanche des opérations du use
case effectuer un achat.
Diagramme de séquence boîte blanche de enregistrer
: un article. ar
:
: v: ldv : art : :
modifier(cecode, v
article
: modifier(
enregistrer un article
create ( date,
art =
create(
maj ligne
la
v
On notera que sur cette représentation nous ne voyons pas bien les associations qui ont été
créées. Par contre nous voyons mieux les objets qui coopèrent pour arriver au résultat. Ces
14
deux représentations du contrat sont assez complémentaires, et mériteraient d'être présentes
toutes les deux.
: v: ldv :
: :
v
dénomb
ldv = sélectionner
modifquan
p=
afficher([Link]
15
s-
Ces schémas sont moins précis que le contrat d'opération sous forme textuelle. Nous n'y
retrouvons pas les associations. Les schémas de diagramme de séquence boîte blanche utilisés
pour décrire un contrat d'opération ( bien que souvent utilisés ) vont introduire des choix de
conception ( qui déclenche les opérations? ). Si ces choix ne sont pas faits le diagramme
induit une erreur d'interprétation. Nous préfèrerons donc faire ces choix de conception dans le
diagramme de collaboration, et garder le contrat d'opération sous forme textuelle.
La conception
dixième étape : le diagramme d’état
16
nouvel
arrivée article /
démarrer
at traitement un
do afficher en boucle
abandon / entry: afficher prix +
entrée quantité/afficher
et
impression en
attente
entry :
impre somme saisie / afficher
Modifierprix(cod 1 :Modifierprix(co
e,prix) :caisse de,prix) :catalogu
e
1.1 :art :=
chercher(code) : article
1.2 :
setprix(prix)
art : article
: article
a) les objets
Ici nous représentons la coopération des objets pour rendre le service demandé. Il s’agit
donc bien d’objets. Ces objets apparaissent sous trois formes :
17
Objet
anon
Modifierprix(cod 1 :Modifierprix(co
e,prix) :caisse de,prix) :catalogu
e
1.1 :art :=
Obje chercher(code) : article
t 1.2 : Mult
nom setprix(prix) i
art : article
: article
Messa
ge Caisse
initial envoie
issu du un
D’abord
Modifierprix(co :caisse 1 :modifierprix(c le
:catalog
de,prix) ode,prix) ue catalogu
e
Puis il
modifie
le prix
de 1.1 :art :=
chercher(code) : article
1.2 :
setprix(prix)
art : article
: article
Le message initial est non numéroté par convention. Ce message est un des événements issu
du diagramme de séquence boîte noire, dont nous avons fait un contrat d’opération.
Les opérations effectuées en réaction à un message numéro x seront numérotées de x.1 à x.n.
Et ainsi de suite pour les opérations effectuées en réaction à un message numéro x.y ( x.y.1 à
x.y.n).
18
Valeu mess
r de age
Type de
1.1 :art := chercher(code) : article retour du
message
Paramè
num affecta tre(s) du
éro tion messag
Un lien entre deux objets est directionnel. La flèche indique le receveur du message. Ce lien
implique que l’objet qui envoie le message connaisse celui qui le reçoit. Un objet ne peut en
effet envoyer un message qu’à un objet qu’il connaît ( rappelez-vous que l’on envoie un
message à un objet en lui parlant : moncatalogue , modifie le prix de l’article de code
moncode à monprix.). Chaque flèche implique une visibilité orientée entre les objets.
:catalogu
e
connaît
Modifierprix(cod 1 :Modifierprix(co
e,prix) :caisse de,prix) :catalogu
e
:caisse
connaît
l’objet 1.1 :art :=
:catalogu chercher(code) :article
1.2 :
setprix(prix)
art : article
:article
Nous avons vu la forme générale des messages. Il peut y avoir quelques formes particulières
de messages, que nous allons lister maintenant.
Le message n’est envoyé à :B que si la condition x>y est remplie lors du déroulement du code
de msg() dans la classe A.
1 :ms :A
1.1a[x>y] :mes :B
g() sageb()
Numér condi
tion
o avec
une
1.1b[ not
x>y] :messagec()
:C
Même
numéro
avec une Condition
contraire
Si la condition x>y est vérifiée, un message est envoyé à l’objet :B, sinon un message est
envoyé à l’objet :C
● Les messages d’instanciation
Ce sont les messages de création d’objet. Ce sont des messages create, avec ou sans
paramètres, qui créeront de nouvelles instances d’objets.
:catalogue Nouvelarticl
1 :Ajouter(desc,prix, 1.1 :Create(desc,prix e
code,qté) ,code,qté)
:Type
article
Ici le message create crée un nouvel article en l’initialisant. Les vérifications d’usage seraient
bien sûr à effectuer ( le code de l’article n’existe t’il pas déjà au catalogue ?).
20
Le message sera envoyé dix fois à l’objet :B. De manière générale l’étoile placée après le
numéro désigne une itération. Nous trouverons soit une énumération de l’itération, comme ici,
une condition de continuation également ( 1.1*[not condition] :message() ). Nous trouverons
ultérieurement une itération sur tous les éléments.
Ce sont des messages itératifs, où plusieurs messages sont envoyés dans l’itération.
1 :ms :A
1.1*[i :=1..10] :me :B
g() ssageb()
Itérati
on sur
i de 1
Ici nous envoyons successivement un message à :B, puis un message à :C, le tout 10 fois. Il
n’y a qu’une seule boucle.
Ce sont des messages itératifs, où plusieurs itérations se suivent. Ici les boucles sont
distinctes.
1 :Create()
Produit frais
1.1 :defDatePérempt
ion(today)
La définition de la date de péremption du produit est faite par lui-même. Il sait combien de
temps il peut se conserver dans des conditions de températures normales. Donc il s’envoie le
message à lui-même.
Le message trouver n'est pas envoyé à l'objet :Type art mais au multi objet, qui se traduira en
programmation par une collection ( tableau, vecteur, hashtable, etc ).
1: lister()
:catalogu
e 22
Cette étoile
montre que
l'on
Pour chaque article du catalogue, nous voulons la
description de l'intitulé de l'article.
Il nous reste ici à imprimer la description obtenue. Pour cela il faut envoyer un message à une
classe.
1: lister()
1.2*:
:catalogu imprimer(s) Systeme
e
1.1*: s :=
getDescr() : String Ici nous avons une
2) Visibilité des objets classe. La
méthode imprimer
est donc une
Pour pouvoir s'échanger des messages, les objets doivent se connaître.
: article méthode statique
:caisse :vente
Ce lien
veut dire
que la
caisse
connaît
La classe caisse doit connaître la classe vente pour pouvoir lui parler:" vente , oh ma vente!
Ajoute-toi un article de ce code.".
La visibilité entre les objets n'est pas automatique. Il y a quatre manières différentes pour un
objet d'en connaître un autre.
23
▪ La visibilité de type attribut.
▪ La visibilité de type paramètre
▪ La visibilité de type locale
▪ La visibilité de type globale
1: desc :=
:caisse getdesc(code):String :catalogu
e
La caisse doit connaître de manière permanente le catalogue. Elle a donc un attribut qui
référence le catalogue. Le lien montre cet attribut, car c'est la seule manière ici de connaître la
classe catalogue.
Supposons que le diagramme de séquence boîte noire mette en évidence un message de type
vérifier avec en paramètre une description d'article. Supposons également que ce soit la caisse
qui traite ce message. Nous obtenons le diagramme de collaboration suivant:
Val := Que
Vérifier(art:article): celui-
Celui-ci c'est là
:caisse le même art :arti
Ici la caisse ne connaît l'article art que temporairement. Le clepassage de paramètre lui fait
connaître l'article art à qui1:
la p := getPrix():
caisse va envoyer un message.
entier
▪ La visibilité de type locale
Setprix(code
,prix)
1:art:= getart(code):
:caisse article :catalogu
e
Récupér
ons une
référenc
2:
setprix(prix art:article Pour
pouvoir
lui
24
Ici caisse va chercher une référence sur un article, pour pouvoir envoyer un message à cet
article. Ici aussi, la connaissance de l'objet est temporaire, mais elle se fait par une variable
locale.
C'est l'utilisation par un objet d'une référence globale à un objet connu de tous. Cela peut aussi
être le cas de variables globales dans le cas de certains langages. Nous comprenons bien que
ce type de visibilité sera utilisé dans de rares cas, où un objet est omniprésent pour tous les
autres objets, et sera considéré comme le contexte de vie de ces objets.
Le problème est que certaines, rares, classes ayant une instance unique doivent être connues
de nombreux objets. Il est alors conseillé d'utiliser le GOF Pattern "Singleton".
Supposons qu'une classe nécessite d'en connaître une autre ayant une instance unique (Par
exemple, le magasin, si l’on avait une classe magasin…), et que les autres techniques de
visibilité soient notoirement malcommodes voici ce que vous pouvez faire:
❖ La classe magasin aura une donnée membre statique instance.
❖ A la création ( constructeur ) du magasin la donnée membre sera initialisée à this
( l'instance nouvellement créée du magasin ).
❖ La classe magasin aura une fonction statique getInstance qui retournera l'instance du
magasin. Ainsi n'importe quelle classe pourra connaître l'objet magasin existant ( car la
méthode étant statique, elle est envoyée à la classe elle même ). Ainsi l'autre classe pourra
envoyer sa requête à l'objet magasin.
25
1: m
:X =getInstanc Magasin
2: xxx()
Ceci Ceci
m:Magas est est
in l'insta une
3) GRASP patterns
Quand un événement est envoyé au système informatique, rien n'indique quelle classe prend
en charge l'événement et le traite en déroulant les opérations associées.
Systeme
informatique
événement A qui
est
envoyé
Système
informatique
:caisse :vente
Qui
crée
la
ligne Creat
e? Creat
e?
:magasin L:ligneve
nte
Creat
e?
26
La réponse à ces questions va influencer énormément la conception de notre application. C'est
ici que l'on va définir concrètement la responsabilité des objets, leur rôle. C'est aussi ici que
l'on va mettre en place la structure du logiciel par couches ( entre autre en implémentant les
passerelles étanches entre les couches ).
Les qualités du logiciel qui lui permettront de vivre, d'être corrigé, d'évoluer, de grandir
dépendront complètement des choix que l'on va effectuer ici.
Les spécialistes de la conception objet, après avoir vécu quelques années de développement
anarchique, puis après avoir testé l'apport de quelques règles de construction, ont fini par
définir un certain nombre de règles pour aider le concepteur dans cette phase capitale et
difficile. Ces règles sont issues de l'expérience d'une communauté de développeurs. Ce sont
des conseils, ou des règles qui permettent de définir les responsabilités des objets. Ce sont les
modèles d'assignation des responsabilités ou GRASP Patterns ( General Responsability
Assignement Software Patterns ).
To grasp ( en anglais ) veut dire: saisir, comprendre, intégrer. Il est fondamental d'intégrer ces
modèles avant de faire de la conception objet, pour obtenir des diagrammes de classe de
conception, et des diagrammes de collaboration de qualité.
Il y a neuf grands principes pour attribuer les responsabilités aux objets, et modéliser les
interactions entre les objets. Cela permet de répondre aux questions:
⮚ Quelles méthodes dans quelles classes?
⮚ Comment interagissent les objets pour remplir leur contrat?
Quand, dans le diagramme de collaboration, un objet envoie un message à un autre objet, cela
signifie que l'objet recevant le message a la responsabilité de faire ce qui est demandé.
da := :catalogue Le
getdescart(cod catalogue à
la
responsabili
Les patterns sont là pour nous aider lors de la conception. C'est un couple problème solution
issu de la pratique des experts.
Nous allons voir les neuf GRASP patterns. Il y a d'autres patterns qui existent ( par exemple
GOF ( Gang Of Four ) patterns ) ceux-là sont plus dédiés à des solutions à des problèmes
particuliers ( par exemple le modèle de traitement des événements ( GOF patterns ) qui a
inspiré le modèle java de traitement des événements ).
Les GRASP patterns sont des modèles généraux de conception, et doivent être considérés par
le concepteur objet comme la base de son travail de conception.
Nous allons étudier chacun de ces patterns.
27
3.1) Faible couplage
Le couplage entre les classes se mesure : c'est la quantité de classes qu'une classe doit
connaître, auxquelles elle est connectée, ou dont elle dépend.
Plus il y a de couplage, moins les classes s'adaptent aux évolutions. Il faut donc garder
en permanence à l'esprit que des liens entre les classes ne seront rajoutés que si nous ne
pouvons les éviter.
Un couplage faible mène à des systèmes évolutifs et maintenables. Il existe forcément
un couplage pour permettre aux objets de communiquer.
Des classes de faible cohésion font des choses diverses ( classes poubelles où sont
rangées les différentes méthodes que l'on ne sait pas classer ), ou tout simplement font trop de
choses.
Ces classes à faible cohésion sont difficiles à comprendre, à réutiliser, à maintenir.
Elles sont également fragiles car soumises aux moindres variations, elles sont donc instables.
Le programmeur doit toujours avoir ce principe de forte cohésion en tête. Il réalisera
alors des classes qui ont un rôle clairement établi, avec un contour simple et clair.
3.3) Expert
Ici nous allons établir qui rend un service. Le principe est que la responsabilité revient
à l'expert, celui qui sait car il détient l'information.
Quelle classe fournira le service getdescription ( code ) qui retourne la description d'un
article dont nous avons le code?
C'est
l'expe
rt
C'est le catalogue qui possède les descriptions d'article, c'est lui l'expert. C'est donc lui
qui nous fournira le service getdescription.
Ce principe conduit à placer les services avec les attributs. Nous pouvons aussi garder
en tête le principe: "celui qui sait, fait".
3.4) Créateur
Quand une instance de classe doit être créée, il faut se poser la question: " Quelle
classe doit créer cet objet?".
28
⮚ A utilise souvent B.
Alors A est la classe créatrice de B. Il arrive souvent que deux classes soient de bons
candidats pour créer une instance. Alors il faut évaluer le meilleur candidat. Cela va dans
le sens du faible couplage.
Ici c'est le catalogue qui crée les descriptions d'articles.
3.5) Contrôleur
Ici nous voyons de fortes dépendances entre les objets du Système informatique et les objets
de l'interface. Si l'interface doit être modifiée, il y a de fortes chances qu'il faille également
modifier les objets du système informatique.
Notons sur cet exemple un autre problème: les classes du système informatique, ici,
connaissent les classes de l'interface utilisateur. Or, ces classes sont liées à un usage
particulier ( une application ) alors que les classes métier sont transverses à toutes les
applications. Elles ne peuvent donc pas connaître les interfaces utilisateur. Ce sont les
interfaces utilisateurs qui vont chercher les informations des objets métier, et non les objets
métier qui affichent les informations.
Les objets de l'interface utilisateur vont solliciter un objet d'interface, plutôt que de solliciter
les objets métier eux-mêmes. Cet objet s'appelle un contrôleur.
29
- Quelque chose du monde réel qui est actif dans le processus: rôle d'une personne impliqué
dans le processus (caissier).
- Handler artificiel pour traiter tous les événements d'un use case. ( AchatHandler )
Le problème est maintenant de savoir comment nous allons choisir notre meilleur contrôleur (
il peut y en avoir plusieurs ).
L'événement entrerunarticle arrive donc sur une des quatre classes: Caisse, Magasin, Caissier
ou AchatHandler.
L'expérience montre que la troisième proposition, appelée contrôleur de rôle, est à utiliser
avec parcimonie, car elle conduit souvent à construire un objet trop complexe qui ne délègue
pas.
Les deux premières solutions, que l'on appelle contrôleurs de façade sont bien utilisées quand
il y a peu d'événements système. La quatrième proposition ( contrôleur de use case ) est à
utiliser quand il y a beaucoup d'événements à gérer dans le système. Il y aura alors autant de
contrôleurs que de use cases. Cela permet de mieux maîtriser chaque use case, tout en ne
compliquant pas notre modèle objet.
Nous avons peu d'événements à gérer. Nous prendrons donc la solution 1 ou 2. Le choix
entre ces deux propositions va se faire en appliquant les patterns précédemment établis.
3.6) Polymorphisme
30
Quand vous travaillez avec des objets dont les comportements varient lorsque les
objets évoluent, ces comportements doivent être définis dans les classes des objets, et les
classes doivent être hiérarchisées pour marquer cette évolution.
Les comportements seront définis par des fonctions polymorphes, c'est à dire ayant même
forme ( même signature ou interface ), mais avec des comportements mutants.
Ainsi lorsque l'on sollicite un objet de cette hiérarchie, il n'est pas besoin de savoir quelle est
la nature exacte de l'objet, il suffit de lui envoyer le message adéquat ( le message
polymorphe ), et lui réagira avec son savoir faire propre.
Cela permet de faire évoluer plus facilement les logiciels. Un objet mutant ( avec un
comportement polymorphe ) est immédiatement pris en compte par les logiciels utilisant
l'objet initial. Un programme ne teste donc pas un objet pour connaître sa nature et savoir
comment l'utiliser: il lui envoie un message et l'objet sait se comporter. Cela va dans le sens
de l'éradication de l'instruction switch ( de JAVA ou de C++).
L'utilisation des différents grasp patterns nous conduit quelque fois à des impasses.
Par exemple la sauvegarde d'un objet en base de données devrait être fait par l'objet lui-même
( expert ) mais alors l'objet est lié ( couplé ) à son environnement, et doit faire appel à un
certain nombre d'outils de base de données, il devient donc peu cohérent.
La solution préconisée, dans un tel cas, est de créer de toute pièce un objet qui traite la
sauvegarde en base de données. Notre objet reste alors cohérent, réutilisable, et un nouvel
objet, dit de pure fabrication, s'occupe de la sauvegarde en base de données.
Cette solution n'est à employer que dans des cas bien particuliers, car elle conduit à
réaliser des objets bibliothèque de fonctions.
3.8) Indirection
Pour éviter le couplage, chaque objet n'a le droit de parler qu'à ses proches. Ainsi,
nous limitons les interactions entre les différents objets.
Quels sont les objets auxquels un objet à le droit de parler?
⮚ Lui-même.
⮚ Un objet paramètre de la méthode appelée.
⮚ Un objet attribut de l'objet lui-même.
⮚ Un objet élément d'une collection attribut de l'objet lui-même.
⮚ Un objet créé par la méthode.
Les autres objets sont considérés comme des inconnus auxquels, nous le savons depuis
la plus tendre enfance, il ne faut pas parler.
Prenons un exemple:
31
L'objet caisse connaît la vente en cours ( c'est un attribut de la caisse ). Cette vente
connaît le paiement ( c'est un attribut de la vente ).
Nous voulons réaliser une méthode de la caisse qui nous donne la valeur de la vente en
cours. Voici une première solution:
1:P:=paiement():
Total = :Caisse Paiement :Vente
totalvente():float
Caiss 2:Total :=
totalvente():float p:Paiem
e et ent
paie
ment
Cette solution implique que l'objet caisse dialogue avec l'objet paiement. Hors a priori
il ne connaît pas cet objet paiement. Pour limiter le couplage entre les objets, il est
préférable d'utiliser la solution suivante:
Total = 1:Total:=totalvent
totalvente():float :Caisse e():float :Vente
Vente et 1.1:Total :=
paiement totalvente():float
se
connaissai
t déjà. :Paieme
nt
Ici, la caisse ne sait pas comment la vente récupère le total. Des modifications de la structure
des objets vente et paiement ainsi que de leurs relations ne changent rien pour la caisse.
Nous allons faire autant de diagrammes de collaboration que nous avons fait de
contrats d'opérations. Pour chaque contrat d'opération nous allons prendre l'événement du
système comme message d'attaque de notre diagramme de collaboration, puis nous allons
créer les interactions entre les objets qui, à partir de là, permettent de remplir le service
demandé. Nous serons vigilant à bien respecter les modèles de conception. Il n'est pas un
32
message qui se construise au hasard. Chaque message envoyé d'un objet à un autre se justifie
par un pattern de conception ( au moins ) .
3.1:ldv :=
créer(art)
1 art := 3.2:
chercherarticle(cod ajouter(ldv)
:Catalog ldv:lignedev
ue ente :lignedev
ente
3.1.1:
1.1 art := p:=getprix()
chercher(code) 3.1.2: desc :=
art:
: article
Article
1.2:miseàjour(q
uantité)
1.1:
ldv :=dernierld
art:
article
33
4.3) Diagramme de collaboration de Finir la vente
Finirvent 1:
e() :Caisse Finirvente :Vente
1.1:
terminé :=
1.2*: somme :=
somme+getsstotal() *
Liste:lign
edevente
1.2:afficher(s-
:Affiche total)
ur
1.3: créer(v) 1.1:
1.4: créer(total
imprimer()
:Paieme
nt
Le :ticket
ticket
doit
connaît
re la
Les classes ticket et vente sont étroitement couplées. Nous pouvons nous poser la question de
savoir si cette classe ticket a un intérêt. Il serait bon de faire l'impression par la vente ( l'expert
), mais en s'appuyant sur une indirection (imprimante) comme pour l'affichage. Enfin une
analyse de tous les uses cases mettrait en évidence le besoin d'archiver les ventes, pour
pouvoir faire les statistiques. Nous allons donc proposer une solution plus avancée de ce
diagramme de collaboration.
On a ici rajouté les notions d’historisation des ventes (qui découle d’autres use-case), ce qui
nous permet de faire apparaître la classe magasin (singleton). Cette classe sera fusionnée avec
la classe catalogue.
34
En ce qui concerne l’affichage, on a choisi d’utiliser un afficheur relié directement à la caisse.
Il aurait pu être judicieux de lier l’afficheur à la ligne de vente (pour l’affichage des
descriptions, prix, quantité et sous-total associés à la ligne de vente) et à la vente (pour
l’affichage du total et de la monnaie à rendre).. Dans ce cas, on aurait pu aussi utiliser le
pattern singleton pour modèliser cet afficheur (un objet unique, accessible depuis partout).
:Affiche :imprim
ur ante
1.2.1:afficher( 1.8.1:
s-total) imprimer(line)
1.7*:line :=
desc+prix+quantité+sstot
2:historis 1.2:afficher(s-
er(v) total)
1.1:
:Magasin créer(total
:Paieme
nt
1.3.1:desc:=ge
tdesc()
1.4.1:prix:=get
Art:
article
35
Tous les détails sur l'impression du ticket n'y sont pas ( entête, total, somme rendue … ), mais
ce schéma donne une bonne vue des relations entre les objets.
Nous allons enfin construire le diagramme de classes de conception. C'est le diagramme qui
nous montre les classes qui seront développées. C'est donc l'aboutissement de ce travail
d'analyse.
Pour l'ensemble des uses cases qui composent notre cycle de développement, nous allons
réaliser le diagramme de classes de conception. Nous allons partir des diagrammes de
collaboration, qui nous donnent les classes à développer, leurs relations ainsi que les attributs
par référence. Nous complèterons ce diagramme par les attributs venant du diagramme de
classe d'analyse.
Nous allons partir uniquement du use case effectuer un achat, donc des quatre diagrammes de
collaboration du chapitre précédent. Etape par étape, nous allons construire le diagramme de
classe de conception.
1) Première étape
Nous allons référencer toutes les classes rencontrées dans les diagrammes de collaboration.
Nous allons les dessiner dans un diagramme de classes. Nous allons y ajouter les attributs
venant du diagramme de classe d'analyse, et les méthodes venant du diagramme de
collaboration.
Nous ne référençons que les classes participant à nos diagrammes de collaboration.
Les collections ne sont pas référencées en tant que telles, cela n'apporte pas de plus value.
Les messages vers les collections n'ont pas lieu d'être, les collections supportant ces
messages par défaut.
Les messages de création par défaut ne sont pas référencés, s'il n'existe pas de
constructeur par initialisation ( car alors ils existent forcément ).
Les sélecteurs et les modifieurs n'ont pas de raison de figurer dans ce schéma pour ne
pas le surcharger. En effet dans la majorité des cas, ces messages existent et sont développés à
la construction de la classe.
Caisse Vente
Date : date
Payervente(s:float) Terminé : booléen
enregistrerarticle(code : Codebarre) Total : float
dénombrer(quantité : entier) payersomme(s:float)
finirvente() Vente(date:Date)
afficher(total:float) ajouter(art: article)
Magasin Lignevente
Paiement
Nom : text Quantité : entier
Adresse : Adresse Sstotal : float Total : float
Descriptionarticle
Afficheur Imprimante
Description : text 36
Prix : réel
Code : codebarre afficher(stt:float) imprimer(line:text)
2) Deuxième étape
Il faut maintenant ajouter les associations nécessaires pour montrer la visibilité par
attribut des objets. Cette visibilité par attribut va permettre de construire les attributs qui
référencent les objets que l'on doit connaître. Cette association porte une flèche de navigation
qui nous permet de connaître l'objet qui doit connaître l'autre.
Prenons trois exemples :
1:payersomme
:Caisse (s) V:Vente
Ici la caisse doit connaître en permanence la vente en cours: nous aurons une
association de type attribut.
Caisse Vente
1 trai 1..
te 0
venteenc
ours
Cela indique que la classe Caisse a un attribut qui référence l'objet vente en cours. Le
nom de rôle venteencours donnera, pour une génération automatique de code, le nom de
l'attribut qui référence la vente en cours.
Ajouter(ar
:Caisse t) :Ventes
Ici la caisse a une visibilité de type variable locale sur l’ article ( cela serait le même
traitement si il avait une visibilité de type paramètre comme pour la Vente ). Nous ajouterons
des dépendances de type relation entre les deux classes.
Caisse article
Flèch
e
avec
37
Cette association montre que le code des méthodes de Caisse doit manipuler la classe
article. Il sera donc nécessaire, à la génération de code, d'inclure un accès à la classe article
dans la classe Caisse ( import java, ou include c++ ).
1:art:=chercherarti
:Caisse cle(code) :Catalogu
e
Ici le magasin est une instance unique pour l'application. Un certain nombre de classes
doivent connaître le magasin. Nous pouvons résoudre le problème comme au 1°. Mais, si
nous ne voulons pas multiplier les références au magasin, nous pouvons appliquer le pattern
Singleton ( Singleton pour instance unique ).
Cela revient effectivement à travailler avec une instance globale, mais fait proprement,
dans des cas rares, et répertoriés. Nous retrouverons la notation de relation entre les classes,
ici pour un accès à une variable globale.
Caisse Magasin
$boutic:Mag
asin
$getInstanc
(exemple en java)
public static Magasin getInstance()
{
if (boutic == null)
{
boutic = new Magasin();
}
return boutic;
}
Ainsi nous aurons une instance unique du Magasin. Cette instance peut être appelée partout en
utilisant le formalisme suivant:
(exemple en java)
Magasin monbazar = [Link]();
38
Afficheur Imprimante
affichet(stt:float) imprimer(line:text)
1 1
Notes sur le diagramme de conception:
possè
poss de
ède ⮚ Quand la vente en cours est créée, il faut lui associer la caisse sur laquelle s'effectue la
vente. Nous avons fait le choix que la vente ne connaisse 0.. que la caisse, plutôt que de
connaître chacun de ses éléments ( afficheur, imprimante,… 1 ) Vente
1
1 ⮚ La caisse joue un double rôle: celui d'interface pour le use Date case: date
effectuer un achat, et
1
l'indirection vers les composants réali
physiques de la caisse. Si la caisse devient trop
Terminé : booléen
Caisse
complexe, il vaut mieux couper se la classe en deux, d'un coté l'interface
Total : float du use case, et de
l'autre côté l'interface vers 1
les composants
Se fait deVenteenc
la caisse elle-même. Nous ne l'avons pas fait
payersomme(s:float)
dans le cadre de cet exercice. sur ours 1 Vente(date:Date,c:Caisse)
Payervente(s:float)
ajouter(art:article)
⮚ Quand entre
enregistrerarticle(code deux classes, il existe déjà une association (
: Codebarre) ), il n'est pas utile
d'y rajouter
: entier)une relation ( Historiq
). Cela n'apporte rien, sinon du bruit.
dénombrer(quantité
ue * 1 1
⮚ Ici, nous n'avons pas rajouté les indicateurs de visibilité ( public +, privé -,…), étant bien
finirvente()
afficher(total:float)
effect
entendu que les méthodes sont publiques, et les attributs sont privés. Les fonctions d'accès
ue
aux attributs sont sous-entendues.
0..
⮚ Les noms de rôle mis enregi
sur certaines associations
1 donnent le nom de l'attribut dans la classe
stre
concernée. Les associations sont unidirectionnelles et donne le sens de la visibilité. Le
Paiement
diagramme nous montre que la classe Caisse a un attribut qui s'appelle venteencours qui
référence la vente en1 cours. De même la: float
classe Magasin a un attribut qui s'appelle
Total
conti
catalogue et qui est une collection d’articles ( c'est la cardinalité qui nous montre
ent que dans
Magasin ce cas c'est une collection ). Paiement(total:float)
Les trois relations présentées sont des trois types possibles ( global, paramètre et local )
Nom : text
39
class Catalogue
{
private Hashtable articles=new Hashtable() ; ;
class Vente
{
private boolean venteterminée = false ;
private Vector lignes = new Vector() ;
public void ajouter(Article art)
{
[Link](new Ldv(art)) ;
}
public void finvente()
{
venteterminée= true ;
}
public float total()
{
float total=0 ;
Enumeration e=[Link]() ;
while ([Link]())
{
total+=((Ldv)[Link]()).soustotal();
}
return total;
}
….
}
class Ldv
{
private int qte ;
private Article art ;
….
public Ldv(Article a)
{
[Link]=a ;
}
public void soustotal()
{
40
return qte*[Link]() ;
}
public denombrer(int newqte)
{
setqte(newqte) ;
}
….
}
class Article
{
private int code=0 ;
private float prix=0 ;
private String desc= “” ;
public Article(int code, float prix, String desc)
{
[Link]=code ;
[Link]=prix;
[Link]=desc;
}
public int getcode()
{
return code;
}
public int getprix()
{
return prix;
}
public int getdesc()
{
return desc;
}
….
}
41