|
|
|
|
|
|
A7- Un attribut dérivé n'a pas de setter; son getter est le calcul à effectuer
'/', introduit dans OMT, indique un attribut calculé à partir des valeurs d'autres attributs; par conséquent, on peut connaître sa valeur, mais on ne peut la modifier directement.
Par exemple, la classe suivante:
pourrait être rendu en Java:
public static final float PI = 3.1416;
private float m_radius;
private float m_area;
public float getRadius() {return m_radius;}
public void setRadius(float radius) {m_radius
= radius}
public float getArea() {return PI*m_radius^2;}
Comparons le UML traditionel avec la nouvelle approche.
Laquelle est la plus avantageuse?
|
Nouvelle approche |
On remarque tout d'abord que la nouvelle approche est plus concise. Bien sûr, la valeur de PI, tout comme celle de area, peut ne pas être affichée pour alléger le graphique. Si les valeurs sont affichés, on sait de quoi dépend la variable area avec la nouvelle approche, alors que cela est totalement caché avec l'approche traditionnelle puisque le corps des méthodes (ici getArea()) n'est pas affiché. Il s'agit pourtant d'une information importante. UML préfére plutôt d'afficher la variable privé radius, cette information étant totalement superflue étant donné la présence de getRadius() et de setRadius() dans le troisième compartiment. Encore une fois UML choisi d'afficher une information inutile et de cacher une information importante. De plus, comme le corps des méthodes est caché, on ne voit pas en UML tradionnel l'impact d'une modification de setRadius() sur la valeur retournée par getArea(); ceci est clair avec la nouvelle approche.
OMT permettait même d'utiliser le même symbole '/' pour
une classe dans le cas où tous ses champs sont dérivés:
![]() |
A8- Un attribut peut avoir un domaine, dans ce cas précondition au setter()
Dans le monde de la modélisation relationnelle,
il existe un concept très pratique qui se nomme un domaine.
Ce concept existe aussi dans quelques langages de programmation objet,
comme le langage Ada. Un domaine est une plage de valeurs possibles (un
range
en anglais) pour une variable donnée. Si par exemple on desire
modéliser une variable altitude, on que l'on sait que jamais l'altitude
sera négative, ni supérieure à une valeur limite (disons
50 000 pieds) dans le cadre d'une application donnée.
![]() |
Il existe un concept en UML que nous aurions pu utiliser pour fixer ces limites; il s'agit des contraintes. Par exemple, sous Flight, nous aurions pu ajouter la contrainte {0 < currentAltitude < 50000) pour restreindre la plage des valeurs possibles de currentAltitude; le hic, c'est que si l'altitude est utilisé 'n' fois dans un modèle, il faut spécifier cette contrainte 'n' fois également. Avec la nouvelle approche, si on décide de faire passer l'altitude maximale de 50 000 à 60 000 pieds, alors on n'a qu'un endroit à modifier.
Si un langage de programmation objet (par exemple Ada 9X) supporte les domaines, alors tout est pour le mieux. Si, c'est un générateur de code de gérer cette fonctionnalité; le dernier cas pourrait être rendu en Java:
public interface Altitude {
public static final int MINIMUM = 0;
public statuc final int MAXIMUM = 50000;
}
public class Flight {
int m_currentAltitude;
public void setCurrentAltitude(int currentAltitude)
{
if ((currentAltitude < Altitude.MINIMUM)
|| ((currentAltitude > Altitude.MAXIMUM)) {
throw new java.lang.IllegalArgumentException();
}
m_currentAltitude = currentAltitude;
} // end setCurrentAltitude()
} //end Flight
Note: java.lang.IllegalArgumentException extends RuntimeException;
A9- nom unique pour les méthodes (pas de surcharge) et paramètres avec valeur par défaut.
En UML tradionnel, on permet l'affichage de plusieurs méthodes ayant le même nom, à la condition d'avoir des signatures différentes. Cela est assez fâcheux, car si l'on décide de ne pas afficher la signature, nous avons plusieurs fois le même nom sans qu'il soit possible de distinguer qui fait quoi; et si l'on décide d'afficher la signature, cela alourdit affreusement le graphique, surtout avec la notation ''parameter : type''.
par exemple: func(String s, int i=8), implante
en Java sous forme de deux fonctions surcharges
d'arite
differentes:
func(String s, int i) {}
func(String s) {func(s,8); }
Le principe d'un nom unique (pas de surcharge au niveau de la conception) est base sur les considerations suivantes:
1) surcharge de functions de meme arite est
(error-prone)
| Avoid overloading methods on argument type. (Overriding
on arity is OK, as in having a one-argument version versus a two-argument
version). If you need to specialize behavior according to the class
of an argument, consider instead choosing a general type for the
nominal argument type (often Object) and using conditionals checking
instanceof. Alternatives include techniques
such as double-dispatching, or often best, reformulating methods (and/or those of their arguments) to remove dependence on exact argument type. Rationale: Java method resolution is static; based on the listed types, not the actual types of argument. This is compounded in the case of non-Object types with coercion charts. In both cases, most programmers have not committed the matching rules to memory. The results can be counterintuitive; thus the source of subtle errors. For example, try to predict the output of this. Then compile and run. class Classifier {
class Relay {
public class App {
|
2) lorsque nous utilisons la surcharge en Java,
c'est une bonne pratique de
definir une methode principale
et d'appeller celle-ci dans les autres
methodes. Reutilisation.
abstract java.io.FilterWriter
public (concrete) void write(int c) throws IOException
public (concrete) void write(char[] cbuf, int off, int len)
throws IOException
public (concrete) void write(String str, int off, int len) throws
IOException
"In order to create your own filtered character output stream, you
should subclass
FilterWriter and override all of its write() methods to perform the
desired
filtering operation. Note that you can implement two of the write()
methods in
terms of the third, and thus only implement your filtering algorithm
once".
Application: HTML code converter, accents in foreign language.
A10- 'S' pour sequential
R int oid = S(0, 1);
pourrait etre rendu:
private int m_oid = 0;
public int getOid() {
m_oid++;
return m_oid;
}
S(init value, step, max value (pour circulaire)).
A11- Un attribut ou groupe d'attribut p-etre declaré unique;
Comme en relationnel, il pourrait être utile de spécifier
des combinaisons uniques, i.e. avec une seule valeur possible. Ces
clefs uniques serainet noté par le prefixe <1>, <2>, etc.
Ainsi, dans le diagramme suivant, on indique qu'il ne peut y avoir deux
employés ayant le même ID dans la même division (deux
employés travaillant dans des divisions différentes peuvent
avoir le même nom). De plus, si deux employés travaillant
dans la même division ont les même noms, alors ils doivent
avoir un discriminant nominale pour permettre de les identifier; ainsi,
dans la même division, nous pourrions avoir un John Doe #1 et un
Jonh Doe #2.
![]() |
Ces contraintes d'unicité devrait être validé
dans les constructeurs et les setters. La façon d'implanter
ces validations est laissé au générateur de code.
A12-La valeur initiale d'un attr peut-etre une methode (et implante dans un constructeur si le langage ne le permet pas).
private int m_value;
Class() {
m_value = setValue();
}
A13- permettre plus d'un stéréotype par concept
UML ne permettant pas les modifiants sur les classes, on utilise des
stéréotypes pour ajouter de l'information sur les classes.
Voici des exemples de stéréotypes couremment utilisés:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Quelques uns de ces stéréotypes peut être modéliser autrement que par des stéréotypes dans UML: une classe abstraite peut être noté en écrivant sont nom en italique, une classe active peut être notée avec une bordure prononcée (bold); une interface peut être représenté par un cercle; mais en général UML ne précise par d'autre manière que l'utilisation des stéréotypes avec les guillemets (<< >>).
On peut noter que la plupart de ces stéréotypes ne sont pas mutuellement exclusifs. Une classe pourrait très bien être à la fois une interface throwable avec une visibilité package et n'ayant aucun état (stateless); une autre pourrait être un classe utility (sans méthode d'instances) stateless (sans variables) et final (sans sous-classes). Un acteur peut être un acteur abstrait, alors pourquoi pas un acteur-interface. Et que fait si un signal est en fait un interface?
Une importante lacune de UML est de ne permettre qu'un seul stéréotype par concept. Si nous avons besoin de définir plus d'un stéréotype à une classe par exemple, une manière de contourner le problème est de définir un stéréotype héritant des deux stéréotypes qu'on veut utiliser; puisque l'héritage multiple est permis sur les stéréotypes. Cepandant, avec vingt stéréotypes, nous avons en théorie besoin de définir près de 200 stéréotypes (20 au carré divisé par deux) et cela, en ne considérant que les combinaisons deux par deux (dans le pratique, le nombre est un peu plus petit, puisqu'un interface n'est jamais final, une exception est toujours throwable, etc.) Si une classe a besoin d'être définie avec un stéréotype héritant de trois stéréotypes de bases, on augmente d'autant le nombre de stéréotypes à définir. Ainsi, pour représenter toutes les combinaisons possibles, les n stéréotypes peuvent être combinés à chacun des autres (n * (n-1)), et ainsi de suite (n * (n-1) * (n-2) * ... 1), donc le nombre de stéréotypes à définir est de l'ordre de n! (n factoriel). On voit facilement que le mécanisme des stéréotypes dans UML est innaplicable.
Une façon de contrer l'explosion combinatoire des stéréotypes est d'utiliser des représentations alternatives à l'utilisation des stérétypes. Des représentations alternatives seront présentées aux points B5 et B6.
De plus, il n'y a pas lieu de d'interdire qu'il y ait plus d'un stéréotype
par concept. Cela n'alourdit pas le diagramme, surtout si on utilise
des icônes au lieu des noms entre guillemets (<< >>).
A14 - Duplicats graphiques
Lorsqu'un classe est rattachée à plusieurs liens d'héritage
et à plusieurs liens d'associations, cela alourdit énornement
le diagramme. Pour contrer le problème, on peut faire plusieurs
représentations graphiques d'une même classe (pour attacher
disons les liens d'héritage à une représentation graphique
et les liens d'associations à l'autre représentation graphique).
Mais en examinant une classe dans UML, il n'y a aucun moyen de savoir si
cette classe a été dupliquée ailleurs dans le diagramme.
Pourquoi, dans le cas des classes représenter graphiquement plus
d'un fois, ne pas indiquer dans le coin supérieur droite de quel
représentation graphique on a affaire. Ainsi un classe notée
1/2 nous indique qu'il s'agit de la première représentation
graphique d'un total de deux représentations graphiques.
|
|
UML utilise une boîte ombragée pour identifier une classe. Si on permettait au classe d'accepter des modifiants (comme la visibilité), alors pourquoi de pas identifier les classes d'utilité avec le symbole '$' préfixé avant le nom de classe. Ce symbole signiefirait que tous les membres (champs et méthodes) sont des membres de classe, rendant ici redondant l'affichage de ce préfixe devant les champs et les méthodes (le modèle devient ainsi plus concis).
Ce symbole signifierait aussi que non seulement cette classe est non-instantiable, mais on plus elle ne contient aucun constructeur (ceux-ci ne pouvant être des ''constructeur de classe'', et ne sont même pas des méthodes, voir point B12). Soit dit en passant, une classe peut très bien être non-instantiable (être abstraite) et contenir quand même des constructeurs, ceux-ci ayant généralement une visibilité protégée et destinés à être appellés (et non overidden) par les constructeurs des sous-classes.
Une classe d'utilité étant en quelque sorte une ''classe statique'', représenter cette classe avec le même préfixe que celui utilisé pour la staticité des membres respecte le principe 4 : ''Des concepts similaires devraient être représentés graphiquement de façon similaire.''.
Il est avantageux de modéliser une classe d'utilité (même si le langage ne le supporte pas) puisqu'il est plus concis de désigner une classe statique que chacun de ses membres.
Note: Java permet l'utilisation de static devant un nom de classe seulement dans le cas d'une classe intérieure (inner class), et dans une optique totalement différente d'une classe d'utilité. Une classe intérieure est une classe dont la visibilité est restreinte à celle de la classe englobante (outer class); une classe intérieure statique est une classe intérieure dont l'instantiation de la classe englobante ne provoque pas l'instantiation de la classe intérieure statique.
A16- UML: concurrent, guarded and synchronized
init block : static ou non
method cannot override with more restrictive visibility
public superclass.method(), subclass.method()
cannot be protected, private..
"les droits donnes en haut ne peuvent etre
retires en bas"
A17 - représentation graphique de l'abstraction, de la staticité, et des autre modifiants
UML a souvent hésité dans la représentation graphique des modifiants. L'abstraction a d'abord été représenté par la chaîne {abstract}, puis par un préfixe. Dans la version la plus récente d'UML, on représente les membres abstraits en italique et les membres statiques en les sous-lignant.
Cette dernière façomn est peu pratique. Par exemple, dans le diagramme de gauche (UML standard), pouvez-vous identifier d'un seul coup d'oeil les membres abstraits et les membres statique? Sachant qu'un membre abstrait ne peut pas être statique (il s'agit d'une contrainte objet), pouvez-vous identifier dans le diagramme de gauche, toujours en un clin d'oeil, les membres qui sont fautivement abtraits et statiques?
Au contraire, en préfixant d'un A les membres abstraits, et d'un
$ les membres statiques (il s'agit du l'ancien formalisme utilisé
par les développeurs de Rose avant d'être ''corrigés''
par les modelisateurs d'UML), le diagramme de droite (UML nouvelle approche)
est beaucoup plus clair. Comme l'oeil humain recherche plus facilement
une chaine comme $A qu'une combinaison des styles italique et sous-ligné,
les methodes fautives 3, 5 et 7 nous sautent au visage dans diagrammes
de droite, alors qu'ils passent inaperçues dans le diagramme de
gauche. Si le but d'UML est d'identifier rapidement les erreurs de modélisation,
alors le diagramme de gauche (UML traditionnel) viole le principe 1 (principe
de perceptibilité). Si un AGL ne permet qu'un caractère comme
préfixe, alors le principe 3 (principe de contrainte) est également
respecté dans le diagramme de droite.
|
|
generalisation -vs- realisation
inner classes
classes instantiees/parametrisables
champ nullable
enumeration