REMARQUES SUR LE DIAGRAMME DE CLASSES DE UML

Partie I
Partie II
Partie III
Partie IV
Partie V

  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?
 


UML traditionnel

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 {
  String identify(Object x)  { return "object"; }
  String identify(Integer x) { return "integer"; }
}

class Relay {
  String relay(Object obj) { return (new Classifier()).identify(obj); }
}

public class App {
  public static void main(String[] args) {
    Relay relayer = new Relay();
    Integer i = new Integer(17);
    System.out.println(relayer.relay(i));
  }
}

     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:
 
 

<<Interface>>
<<Exception>>
<<Throwable>>
<<Final>>
<<Public>>
<<Package>>
<<Boundary>>
<<Entity>>
<<Control>>
<<Signal>>
<<Stateless>>
<<Active>>
<<Abstract>>
<<Type>>
<<Actor>>
<<Implementation>>
<<Metaclass>>
<<Powertype>>
<<Process>>
<<Thread>>
<<Utility>>

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 traditionnel
Nouvelle approche

 A15 - classe d'utilité

UML définit une classe d'utilité (utility class) comme une classe non-instantiable, dont tous les méthodes sont des méthodes de classe (aucune méthode d'instance).  Ce concept est inconnu dans la syntaxe Java(voir note plus bas), mais est néanmoins utilisé (le meilleur exemple est sans doute la classe java.lang.Math, dont toutes les méthodes, sin(), cos(), ect., dont des méthodes de classe).

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.
 

Class
(fields)
method1()
method2()
method3()
method4()
method5()
method6()
method7()
method8()
A Class
(fields)
$    method1()
A   method2()
$A method3()
$    method4()
$A method5()
A   method6()
$A method7()
      method8()

 

generalisation -vs- realisation

inner classes
classes instantiees/parametrisables
champ nullable
enumeration