Professional Documents
Culture Documents
Le problme
Cest un problme trs simple, afin quil puisse tre trait compltement. Il est tir de louvrage Applying UML and Patterns de Craig Larman (et des transparents de Pascal Molli, UHP Nancy). Il sagit dun jeu de ds. Le joueur lance 10 fois 2 ds. Si le total des 2 ds fait 7, il marque 10 points son score. En fin de partie, son score est inscrit dans le tableau des high scores.
Play Player
2.2. Un premier diagramme dactivits Sa construction permet de dcrire lorganisation gnrale des traitements et permet de dialoguer avec les clients. On peut complter ce diagramme par des maquettes dcran associes aux diffrentes oprations.
menu [highscore] [start] Start turn=0 Roll Dice turn++ [true] Turn<10 [false] Update highscore [exit]
view Highscore
Il doit exister une certaine cohrence entre ces diagrammes. On peut vrifier que tous les cas se retrouvent quelque part dans le diagramme dactivits.
menu [highscore] view Highscore [start] Start turn=0 Roll Dice turn++ [true] Turn<1 [false] Update highscore [exit]
Play Player
d1 : Die
3.2. Un premier diagramme de classes On complte les classes et on reprsente leurs associations statiques et dynamiques ; on dfinit les attributs des classes et les cardinalits des associations.
Player name : String score : int = 0; play( ) 1 Plays 1 DiceGame 1 Includes 1 Rolls 2 Die faceValue : int = 1 roll( ) 2
1 Scoring 1 HighScore
Scoring 1 HighScore
3.3. Des diagramme de squence Ils modlisent la dynamique (~ comme diagrammes de collaboration) en se focalisant sur lenchanement des messages.
: DiceGame : Player d1 : Die d2 : Die
1: play( )
2: roll( ) 3: roll( )
On peut aller plus loin en modlisant la cration des objets en dbut de partie. A noter que le joueur ( :Player) nest cr quau dmarrage de la partie. Cest un choix qui dcoule du diagramme dactivit. On aurait tout aussi bien pu crer le joueur au moment ou lon cre le jeu. Le petit avantage est que lon peut changer de nom entre 2 parties
: RealPlayer : DiceGame d1 : Die d2 : Die : Player
1: DiceGame( )
2: Die( )
3: Die( )
4: start( ) 5: Player(String)
3.4. Un diagramme dtats Il a pour objet de dterminer les tats et transitions dtat dun objet. Ici le diagramme dtats dune partie (diceGame).
cancel Start game Ready to play start Player ready entry: get player name
Quit
Cancel
[ turn>=10 ]
Cancel start
Player ready entry: get player name
Quit Cancel
play
[ turn>=10 ]
Cancel ?
Ces incohrences mises en vidence par le diagramme dtats conduisent modifier les diagrammes prcdents ainsi que les maquettes dcrans envisages lors de lanalyse des besoins.
[exit]
view Highscore
cancel
cancel
3.5. Le problme de larrt de lanalyse Il faut se demander si les cas dutilisation sont couverts correctement par lanalyse. La rponse est plutt ngative.
menu
[highscore]
[exit]
Play Player
view Highscore
turn++
View High Score [true] Turn<10 [false]
Update highscore
Il faut donc reprendre les schmas pour grer le highscore (cration et mise jour).
: RealPlayer 1: DiceGame( )
: DiceGame 2: Die( )
d1 : Die
d2 : Die
: HighScore
3: Die( ) 4: Highscore( )
: DiceGame
: Player
d1 : Die
d2 : Die
new : Entry
: HighScore
1: Player(String)
6: add(Entry)
Rolls 2
Includes
Lanalyse des cas est peu prs complte. On pourrait encore raffiner un peu la dynamique du cancel et les diagrammes de squence du jeu. Passons la phase de conception de la solution.
4. La conception
4.1 Larchitecture gnrale On fait le choix dune architecture en couches (pattern architectural Layer vu en cours) comportant trs classiquement 3 couches.
Prsentation
Application
Play View High Score
Persistance
Fichier ou BDD
Il correspond cette architecture des paquetages et des dpendances entre paquetages. Le paquetage Util regroupe les services qui ne se rattachent pas clairement aux niveaux.
<<layer>> UI
<<layer>> Core
<<subsystem>> Util
<<layer>> Persist
4.2. Conception du niveau applicatif Core On cherche appliquer diffrents design patterns permettant de mieux structurer les classes.
a) le pattern singleton Assure quune classe une instance unique quelle garde dans une variable de classe (static uniqueInstance) et donne un point daccs depuis la classe cette instance (mthode de classe static Instance() ou getInstance()).
Conception
HighScore $ hs : HighScore = null Highscore() add() load() save() 1 0..*
Analyse
Player name : String 1 score : int = 0; play() Player() 1 Plays Rolls 2 Die faceValue : int = 1 roll() Die() 2
Singleton...
Includes 1
-dies
Sont galement ajoutes des mthodes pour sauvegarder les high score (load, save) et pour afficher les ds et joueurs (display). b) le pattern observer Un objet (le sujet) est li plusieurs objets (les observateurs ou observers) pas ncessairement connus la cration du programme. Toute modification est automatiquement rpercute (notifie) tous les observateurs.
Ici, les ds et les joueurs sont les sujets (classe Observable) et les vues sur les ds et sur les joueurs sont les observateurs (interface Observer).
Observable changed : boolean = false Observable() addObserver() deleteObserver() notifyObservers() deleteObservers() setChanged() clearChanged() hasChanged() countObservers() <<Interface>> Observer 0..* update(o : Observable, arg : Object) : void
Panel Panel()
Die DieView faceValue : int = 1 roll() Die() display() PlayerView PlayerView(player : Player) update(o : Observable, arg : Object) : void DieView(die : Die) update(o : Observable, arg : Object) : void
Les vues sont les lments daffichage des objets. Ils doivent voluer ds que lobjet observ change. Do le recours au pattern Observer avec notifyObservers et update(). Les vues sont des paneaux (Panel) au sens de linterface utilisateur (ici awt de Java), cest dire une zone dune fentre.
2: PlayerView(Player)
3: addObserver(Observer)
4: return component
5: display()
6: DieView(Die) 7: addObserver(Observer)
8: return component
4: update(Observable, Object)
o Randomizer est une classe qui rend une valeur au hasard (pour le tirage du d entre 1 et 6). Il faut noter que les interfaces utilisateurs (comme awt en Java) se chargent de propager les vnements sur les lments dinterface (bouton appuy, valeur saisie, clic ou dplacement de souris, fermeture de fentre, ...) vers les classes applicatives. Le pattern Observer fait le travail oppos : il notifie les changements dtat des classes applicatives aux lments de linterface utilisateur. 4.3. Conception du niveau interface utilisateur UI Il faut dfinir les fentres graphiques (forms) contenant ventuellement les panneaux vues. Toutes les forms hritent de la classe awt Frame.
Frame
(from awt)
PlayerForm ok_action() cancel_action() PlayerForm() 0..1 MainForm HighScoreForm ok_action() quit_action() start_action() high_action() MainForm() RollForm 0..1 roll_action() cancel_action() RollForm() 1 PlayerView HighScoreView update() <<Interface>> Observer PlayerView() update() 2 DieView DieView() update()
update()
: RealPlayer
: DiceGame
: MainForm
: PlayerForm
:Player
: RollForm
1: getInstance( )
2: MainForm( )
3: start_action( ) 4: PlayerForm( )
<<Interface>> Displayable display() 2 2 Die faceValue : int = 1 roll() Die() display() setValue()
Singleton...
RollForm
Classes techniques UI
DieView
2 +theDieView
DieView() update()
<<Interface>> Displayable
Observable 0..*
<<Interface>> Observer
4.4 Le paquetage Util Il comprend la classe Randomizer qui utilise la classe Java Random. Encore un singleton !
Singleton : Player : Die : Randomizer : Random
1: roll( )
2: getInstance( ) 3: Randomizer( )
Random Singleton !
Random() nextInt()
5: getValue( )
6: nextInt(int)
4.5 Le niveau persistance Persist Il contient les classes techniques de persistance. Lobjectif est dassurer l indpendance Core/Persist afin de pouvoir utiliser plusieurs types de persistance.
Par exemple, la srialisation (persistance dobjets lis dans un fichier) et lutilisation dune base de donnes relationnelle (via JDBC). Pour cela, on fait appel au pattern Factory. Ce pattern sert quand une classe peut crer des objets (ProduitConcret) de diffrentes classes. Une interface unique (Fabrication) est implante par diffrentes classes concrtes pour chaque type dobjet crer (FabricationConcrte). La classe qui veut crer les objets peut ne travailler quavec les interfaces.
Produit Fabrication Fabriquer() UneOperation() produit=Fabriquer()
Produit abstrait
Produit
concret
Fabrique concrte
JdbcKit makeKit()
SrKit makeKit()
Fabrique abstraite
PersistKit makeKit()
: RealPlayer
: DiceGame
: SrKit
: HighScoreSr
4: makeKit( )
5: HighScoreSr( ) 6: load( )
7: quit( )
8: getInstance( ) 9: save( )
Dans le cas srialisable, la persistance se propage automatiquement de lobjet racine (highscore) tous les objets dpendants (entries).
class HighScoreSr extends HighScore implements Serializable { ... public void save() throws Exception { FileOutputStream ostream = new FileOutputStream(filename); ObjectOutputStream p = new ObjectOutputStream(ostream); p.writeObject(this); // ecrit le highscore et toutes les entries p.flush(); ostream.close(); // ferme le fichier de serialisation } public void load() throws Exception { FileInputStream istream = new FileInputStream(filename); ObjectInputStream q = new ObjectInputStream(istream); HighScoreSr hsr = (HighScoreSr)q.readObject(); }
Dans le cas de JDBC, une table doit tre cre dans un SGBD relationnel. A la cration de HighScoreJDBC il y a connexion au SGBD via JDBC. Un save consiste faire des inserts pour chaque entry . Un load consiste faire un Select * from ..., parcourir le rsultat et crer les objets entry correspondants.
public class HighScoreJDBC extends HighScore { public static final String url="jdbc:odbc:dice"; Connection con=null; public HighScoreJDBC() { try { //charge le driver JDBC Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); con=DriverManager.getConnection(url, "toto",""); } catch (Exception e) { e.printStackTrace(); new Error("Cannot access Database at"+url); } hs=this; // enregistrement de linstance unique! this.load(); } public void load() { try { Statement select=con.createStatement(); ResultSet result=select.executeQuery ("SELECT Name,Score FROM HighScore"); while (result.next()) { this.add(new Entry(result.getString(1), result.getInt(2))); } } catch (Exception e) { e.printStackTrace(); } public void save() { try { for (Enumeration e = this.elements() ; e.hasMoreElements() ;) { Entry entry=(Entry)e.nextElement(); Statement s=con.createStatement(); s.executeUpdate("INSERT INTO HighScore (Name,Score)"+ "VALUES('"+entry.getName()+"',"+ entry.getScore()+")"); } } catch (Exception e) { e.printStackTrace(); } }
Remarque : highscore est une collection dentres (do le this.elements()). 4.6 Le diagramme de composants
Dice Vizualization
Un interface
File System
5. Limplantation
5.1. Gnration du code Traduire la conception dtaille dans un langage objets (Java, C++, Smalltalk, ...) ou non (C, VB, ...). Cest videmment plus direct vers un langage objets ! Exemple : Traduction en Java des composants :
<<Interface>> Displayable display()
package Core; import import import import Util.Randomizer; UI.DieView; java.util.*; java.awt.Component;
Die faceValue : int = 1 roll() : int Die() display() : java.awt.Component setValue(value : int) : void getValue() : int
public class Die extends Observable implements Displayable { private int faceValue = 1; public int roll() { setValue(Randomizer.getInstance(). getValue()); return getValue(); } public java.awt.Component display() { Component c=new DieView(this); this.addObserver((Observer)c); return c; } public void setValue(int value) { faceValue=value; this.setChanged(); this.notifyObservers(); } public int getValue() {
HighScore
(from Core)
Entry
(from Core)
Les environnements de conception (ateliers) UML donnent des outils de gnration de code (forward engineering), de rtro conception partir du code (reverse engineering). La combinaison des deux tant appele round trip engineering. Lobjectif est dassurer en permanence la cohrence de lanalyse, de la conception et du code. Un exemple de gnration :
Die Player
(from Core) (from Core)
package Core; import java.util.*; import java.awt.Component; import UI.HighScoreView; public abstract class HighScore extends Observable implements java.io.Serializable, Displayable { protected static HighScore hs = null; public Vector entries=new Vector(); public void add(Entry entry) { entries.addElement(entry); this.setChanged(); this.notifyObservers(); } public Enumeration elements() { return entries.elements(); } public abstract void load(); public abstract void save(); public Component display() { // implantation de Displayable Component c=new HighScoreView(this); this.addObserver((java.util.Observer)c); return c; } public static HighScore getInstance() { if (hs==null) new Error("No Persist Kit declared");
+theDiceGame DiceGame
(from Core)
// Source file: c:/UML/Core/DiceGame.java package Core; public class DiceGame { private static int dg = null; private Die dies[]; private Player thePlayer; DiceGame() {} /** @roseuid 37F877B3027B */ private DiceGame() {} /** @roseuid 3802F61403A0 */ public void getInstance() {} /** @roseuid 37F8781A014D */ public void start() {} /** @roseuid 38074E7F0158 */
Vector
(from util)
+entries
Player() die1() DiceGame die2() -thePlayer play() display() DiceGame() getName() getInstance() getScore() start() getTurn() getDie() setTurn() getPlayer() -$dg setScore()
-name
-name String
(from lang)
Rien nest possible pour les aspects dynamiques. Il sagit donc dune aide limite. 5.2. Dernires amliorations A la fin de lanalyse on avait signal des manques du ct de la dynamique du cancel et de la gestion du jeu qui tait dfinie par :
: DiceGame : Player d1 : Die d2 : Die new : Entry : HighScore
1: Player(String)
6: add(Entry)
Rien nayant t fait, il faut reprendre ces aspects en liaison avec la conception dtaille actuelle. Le schma prcdent ne gre pas les 10 tours de jeu et la fin du jeu. La version remanie doit correspondre au fonctionnement ci dessous.
1: actionPerformed(ActionEvent)
2: rollAction( )
6. Les tests
Il faut dabord tester toutes les mthodes, classe par classe (tests unitaires). Puis il faut tester les composants (tests dintgration), et enfin, le systme tout entier (test dacceptation au regard des use case et du diagramme dactivits initial dfinissant les besoins).
Vis vis des cas dutilisation et de leur description tout semble correct. Vis vis du diagramme dactivits, il faut tester tous les chemins possibles. Regardons ce point.
[exit]
cancel
cancel
Le chemin normal start, roll*, highscore, exit marche correctement. Par contre le chemin highscore, exit plante ! Il sagit dun problme de (mauvaise) conception. En effet, cest DiceGame qui cre Highscore lors du start. Il faut corriger ! Enfin, le chemin highscore, start, roll* doit voir le highscore (dont la fentre est dj ouverte) voluer dynamiquement si le MVC fonctionne correctement. Cest bon !
7. Conclusions
Lanalyse des besoins a utilis cas dutilisations + descriptions, diagramme d activits, prototypage de linterface utilisateur. Lanalyse a utilis pour la dynamique : les diagrammes de collaborations, squences, tats, pour la statique : les diagrammes de classes. La conception a utilis : le pattern architectural Layer les diagrammes de paquetages, de composants, de dploiement, les design patterns Singleton, Observer, Factory, des classes techniques pour la persistance et linterface utilisateur. La ralisation a utilis les outils de gnration et de rtro conception. Il est indispensable de mettre jour la documentation danalyse et de conception. Les tests ont utilis les cas dutilisation (couverture des fonctionnalits) et le diagramme dactivit (conformit). Bien entendu sur des grosses applications les dmarches de test sont plus complexes (intgration, non rgression, ...).
UML permet davoir plusieurs points de vue chaque tape et de rflchir en termes de couverture et de cohrence. Le travail est facilit avec un atelier UML.
Play Pyr l e a
Cohrence !! Couverture !!
Fd eeae nBvrg i [ no coffee ] [n ca] oo l [f u dc f e] on o e [on ca] fud o l P tC fe nFe u o ei t r l i Ad We o Rsro d ar t ee r t vi G Cs e u t p Gt Cn o Ca e a f o l PutFilterinMachine
DicePersist HighScore
DiceSystem
Displayable
Dice Vizualization
ViewHighScore
Randomizer
JBDC Connection Save/loadthe highscore
d :D 1 e i
T r o M cn un n ahe i
Pyr ae l
^oe o u O cf Pt r n e .n T B w oe r Ce e f
2r =o( :1 r l)
Rolls Die f c V l ei t 1 a e au :n=
(mUeCs Vw r s ae e ) f o i
n m :Si g ae t r n so :n=0 1 cr it ; e
lightgoesout
2 r( o l) 1
py a( l) 1
P uC f e or o e Dk ee g r Bvr e n a i
d :D 2 e i
I cu e nl d s 1 D ae cm e iG 11 Scoring 1 HighScore
/ Sat g m tr a e Ready to play cancel
Py as l
Mm :Pyr o o ae l
vue architecturale
:DiceGame : Pa e lyr d1 : Die d2:Die 1: play( ) 2: roll( ) 3: roll( )
vue fonctionnelle
start
Cancel
[ turn>=10 ]
vue statique
vue comportementale
UML permet de documenter les choix danalyse et de conception. Grce au processus de dveloppement matris, le produit est conforme ce qui tait prvu au dpart. Grce aux patterns, le produit est volutif (on peut facilement modifier linterface ou la persistance). Grce Java, le produit est portable (systme, SGBD). ANNEXE : structuration finale de lapplication
Observable
(from util)
Vector
(from util)
String
(from lang)
+entries
-name -name
Core
#$hs
Player score : int = 0 turn : int = 0 WIN_NUMBER : int = 7 WIN_SCORE : int = 10 Player() die1() die2() play() display() getName() getScore() getTurn() setTurn() setScore()
-dies[] -thePlayer
DiceGame DiceGame() getInstance() start() getDie() getPlayer() -$dg <<Interface>> Displayable display()
UI (1)
ActionListener
(from event)
Frame
(from awt)
HighScoreForm actionPerformed() HighScoreForm() closeAction() -hf -mf MainForm -tf +m_MainForm RollForm actionPerformed() rollAction() cancelAction() RollForm() -close -quit +ok +cancel -start Button
(from awt)
TextField
(from awt)
-rf
HighScoreForm
UI (2)
+cancel
-m_RollForm
+m_RollForm
-l List
(from awt)
Observer
(from util)
Persist
Serializabl
(from io) e
DiceGam
(from Core) e
Entr HighScor e (from Core) +entrie (from util) HighScore( s add() ) elements( #$hs load() ) save( display( ) getInstance ) () Vector
(from Core) y
Util
-random
Random
(from util)
-$r