Syndication : Atom 1.0  RSS 2.0
Blogs des développeurs   »   adiGuba:Blog

Article complet: Exercice Java : POO, encapsulation et immuabilité

03/11/2008

Permalink 17:37:22, Catégories: Java, Langage(s), Exercices, 815 mots   French (FR) , adiGuba

[Java] Exercice Java : POO, encapsulation et immuabilité

Aujourd'hui j'inaugure un genre nouveau avec de petites exercices en rapport avec Java, dans l'objectif de mieux comprendre les rouages et les particularités du langage.

Nous allons donc voir qu'avec une mauvaise conception d'une classe toute simple, il est possible de "casser" le principe encapsulation, si chère à la POO, et qui permet à une instance de classe de protéger ses attributs d'éventuelles modifications externes...


[Suite:]

Prenons donc la classe suivante dont le code complet se trouve ci-dessous :

 
import java.util.Calendar; 
import java.util.Date; 
 
public final class MyObject { 
  
  private final Date date; 
  private final Number number; 
 
  public MyObject(Date date, Number number) { 
    // On vérifie que la date soit plus récente que le 1 janvier 2000 : 
    Calendar cal = Calendar.getInstance(); 
    cal.set(2000, java.util.Calendar.JANUARY, 1); 
    if (cal.before(date)) { 
      throw new IllegalArgumentException("'date' doit être supérieur à l'an 2000"); 
    } 
    this.date = date; 
    
    // On vérifie que l'objet Number soit bien un nombre positif 
    if (number.intValue() < 0) { 
      throw new IllegalArgumentException("'number' doit correspondre à un nombre positif !"); 
    } 
    this.number = number; 
  } 
 
 
  public void hello() { 
    System.out.println("Date   : " + this.date); 
    System.out.println("Number : " + this.number.intValue()); 
    System.out.println(); 
  } 
} 

Il s'agit donc d'une classe finale (qui ne peut donc pas être redéfinie), qui dispose de deux attributs à visibilités privés. Ces deux attributs sont également finaux (ils ne peuvent pas être réassignés par la suite), et leurs valeurs sont vérifiées lors de la construction de l'objet, selon les règles suivantes :

  • Le premier attribut, de type java.util.Date, doit correspondre à une date venant après le 1er janvier 2000.
  • Le second attribut, de type java.lang.Number, doit correspondre à un nombre avec une valeur entière positive.

Bien entendu, la classe ne possède pas de méthode permettant de modifier ses attributs, mais simplement une méthode hello() qui permet d'afficher la valeur de ces attributs.

Enfin, le code s'exécutera dans un environnement protégé par un SecurityManager, ce qui fait qu'on ne pourra utiliser la méthode "barbare" setAccessible(true) de l'API de Réflection pour enfreindre les règles d'encapsulations.

L'objectif sera donc de modifier la valeur des attributs de la classe MyObject et de leurs donner des valeurs incorrects, le tout sans toucher au code de cette dernière.

L'exercice se basera sur le code suivant :

 
public class Main { 
 
  // La méthode main() ne doit pas être modifié ! 
  public static void main(String[] args) throws Exception { 
    // On défini un SecurityManager : 
    System.setSecurityManager(new SecurityManager()); 
    // Et on appelle la méthode exercice() : 
    exercice(); 
  } 
  
  // Exemple de base : 
  public static void exercice() { 
    Date date = new Date(); // Date actuelle 
    Number number = new Integer(100); // 100 
 
    MyObject obj = new MyObject(date, number); 
 
    obj.hello(); 
    
    obj.hello(); 
  } 
}

Donc les questions sont :

  1. En modifiant uniquement le code de la méthode exercice(), comment faire en sorte que les deux appels à obj.hello() affichent des valeurs différentes pour la même instance de l'objet ?
    On pourra pour cela utiliser toutes les classes et méthodes de l'API standard, et créer autant de nouvelles classes ou méthodes que neccessaire.

    L'idéal serait d'obtenir un résultat de la forme suivante :

     
    Date   : Mon Nov 03 17:19:08 CET 2008 
    Number : 100 
     
    Date   : Thu Jan 01 01:00:00 CET 1970 
    Number : -100 
    
  2. Existe-t-il dans l'API standard des classes qui ne posent pas ce genre de problème lorsqu'elles sont utilisées comme attribut ?

  3. Comment corriger la classe MyObject pour corriger ce problème ?

Alors ? Avez-vous trouvé l'origine du problème ?


Note 1 : Il faut bien sûr appeler la méthode hello() de la classe MyObject !
Note 2 : La date du premier janvier 1970 correspond tout simplement à un new Date(0L)
Note 3 : La solution pour la date est la plus évidente, mais dans les deux cas la solution est très proche.

Social Bookmarking:

                                     

Commentaires:

Connectez-vous pour vous abonner à cet article:

Flux de commentaires pour cet article : Atom 1.0  RSS 2.0
Commentaire de: Benet [Membre]
Voilà déjà la solution pour le champ de date:

 
obj.hello();  
   
<b>date.setTime(0L);</b> 
   
obj.hello();  

Permalien 03/11/2008 @ 19:51
Commentaire de: Benet [Membre]
Tiens, on ne peut pas utiliser de balises XHTML dans une balise <code>.
Ah ben c'est logique en fait
Permalien 03/11/2008 @ 19:53
Commentaire de: Laurent Simon [Membre] · http://archiblog.stratic.fr/
Number, ce n'est pas trop contraignant comme contrat..
Permalien 04/11/2008 @ 06:34
Commentaire de: Hikage [Membre] · http://hikage.developpez.com
1. Concernant la Date, cette classe n'est pas immuable et peut etre modifier via setTime.
Concernant le champ Number, il suffit de sous classer la classe Number pour en faire une classe non immuable. Il sera donc possible de modifier le comportement de la méthode intValue();

2. Si l'on utilise les classes filles de Number, il n'y a pas ce problème. Celle-ci sont finales et immuables. Donc impossible de changer le comportement de celles-ci sans utiliser l'API Reflection.

3. Concernant la manière de corriger MyObject, il suffira de ne pas stocké directement la référence passée dans le constructeur mais de faire une copie :

 
  public MyObject(Date date, Number number) {  
  // On vérifie que la date soit plus récente que le 1 janvier 2000 :  
  Calendar cal = Calendar.getInstance();  
  cal.set(2000, java.util.Calendar.JANUARY, 1);  
  if (cal.before(date)) {  
  throw new IllegalArgumentException("'date' doit être supérieur à l'an 2000");  
  }  
  this.date = new Date(date.getTime()); 
   
  // On vérifie que l'objet Number soit bien un nombre positif  
  if (number.intValue() < 0) {  
  throw new IllegalArgumentException("'number' doit correspondre à un nombre positif !");  
  }  
  this.number = createNumberCopy(number); 
  }  
 
 

Permalien 04/11/2008 @ 08:30
Commentaire de: adiGuba [Membre] · http://adiguba.developpez.com
1. Oui c'est tout à fait cela. Dès lors qu'on utilise un type non-immuable comme attribut, on doit s'attendre à ce qu'il puisse être modifié depuis l'extérieur de la classe.
  • Date est mutable via sa méthode setTime().
  • Number n'est pas directement mutable (aucun accesseur), mais comme elle peut être dérivée et elle n'est donc pas immuable. Ses classes filles peuvent très bien changer le comportement de ses méthodes, et c'est le cas plus précisément de la classe AtomicInteger...


Le code de la méthode exercice() pourrait donc ressembler à cela :
   public static void exercice() {  
   Date date = new Date(); // Date actuelle  
   AtomicInteger number = new AtomicInteger(100); // 100  
    
   MyObject obj = new MyObject(date, number);  
    
   obj.hello(); 
    
   date.setTime(0L); // 1er janvier 1970 
   number.set(-100); 
    
   obj.hello();  
   }



2. Plus précisément que les classes filles de Number, il faut utiliser des types immuables, c'est à dire dont la valeur ne varie jamais au cours de l'exécution. Par exemple : Integer, Double, String,...

Pour rappel la valeur d'un type immuable est déterminé lors de sa création, et ne peut pas être modifié par la suite...


3. C'est presque cela... puisqu'avec ce code il est encore possible de passer outre les vérifications du constructeur (c'est un peu tordu mais c'est possible).


a++
Permalien 04/11/2008 @ 09:59
Commentaire de: lunatix [Membre]
et au fait ? vous pratiquez la protection via copie des attributs mutables dans vos projets ? j'avoue que j'hésite souvent, j'ai finalement rarement vu de bugs provoqués a cause d'une modification intempestive de variable.
Permalien 04/11/2008 @ 15:08
Commentaire de: djo.mos [Membre]
lunatix> pareil pour moi.
Mais je crois que ça ne s'applique pas à ceux qui développent des applications prêts à porter si on peut dire, plutôt aux développeurs d'APIs qui seront utilisés par d'autres programmeurs ...
Permalien 04/11/2008 @ 15:16
Commentaire de: adiGuba [Membre] · http://adiguba.developpez.com
Lunatix > Là est toute la question ;)

S'il y a peu de bugs de ce genre, c'est surtout pour deux raisons :
  • Les types primitifs ne sont pas concerné par ce problème.
  • La plupart des classes conteneurs de base sont immuable, et n'ont donc pas besoin de copie de protection.



Si la classe String n'était pas immuable, on rencontrerait ce genre de problème plus souvent ;)

a++
Permalien 04/11/2008 @ 15:26
Commentaire de: lunatix [Membre]
c'est sur que String immutable, ça aide bien ;)
a chaque fois que je passe findbugs sur un projet, j'ai le dilemme...

mais effectivement, je suis d'accord avec toi djo.mos, si je devais designer une api, je serai plus attentif a ca.
Permalien 04/11/2008 @ 18:41
Commentaire de: adiGuba [Membre] · http://adiguba.developpez.com
Pour compléter la réponse de Hikage : lorsqu'on récupère un type non-immuable, on doit d'abord faire une copie AVANT de vérifier la valeur, puis de vérifier la valeur de cette copie.

Par exemple au lieu d'avoir ceci :
 
  if (number.intValue() < 0) {  
  throw new IllegalArgumentException("'number' doit correspondre à un nombre positif !");  
  }  
  this.number = new Integer(number.intValue()); 


on devrait avoir celui-ci :

 
  this.number = new Integer(number.intValue()); 
  if (this.number.intValue() < 0) {  
  throw new IllegalArgumentException("'number' doit correspondre à un nombre positif !");  
  } 


Car pour une classe muable, rien ne garantie que deux appels successif de la même méthode renverront bien le même résultat.

En effet on pourrait avoir par exemple une implémentation comme celle-ci :
 
class RandomNumber extends Number { 
  @Override 
  public int intValue() { 
    // Nombre aléatoire entre -5000 et 5000 
    return (int) ( (Math.random() * 10000) - 5000 ); 
  } 
  
  // ... 
}



En clair si on veut protéger ses attributs, on ne devrait jamais faire confiance aux classes muables ;)


a++
Permalien 07/11/2008 @ 13:42
Commentaire de: benwit [Membre]
A propos de ton dernier post dans ce billet,

Dans le premier exemple, d'accord puisque tu appelles deux fois de suite la méthode du Number dont tu ne connais pas l'implémentation (RandomNumber par exemple)

Mais dans le deuxième, tu n'appelles qu'une fois la méthode du Number dont tu ne connais pas l'implémentation donc ça ne devrait pas causer de soucis ?
Si dans cette deuxième méthode, tu avais enlèver le "this" dans le test, je suis de nouveau d'accord, ça peut poser problème.

Quelque chose m'aurait échappé ?

A quand les nouveaux jeux ? ;o)

Permalien 27/08/2009 @ 20:00
Commentaire de: adiGuba [Membre] · http://adiguba.developpez.com
benwit > Non tu as raison : la seconde méthode correspond au code correct puisqu'on n'accède qu'une seule fois aux données. C'est ce que je voulais dire je me suis mal exprimé ;)


Sinon pour de nouveaux jeux il faudrait que j'y penses, mais je recommence petit à petit à blogguer un peu ;)

a++


Permalien 27/08/2009 @ 20:56
Commentaire de: benwit [Membre]
En fait, il manque juste entre les deux exemples un texte "on devrait avoir celui-ci" qui ferait le pendant à "Par exemple au lieu d'avoir ceci" car suite à ta réponse, j'ai relu le début et c'est super limpide.
Permalien 27/08/2009 @ 21:23
Commentaire de: adiGuba [Membre] · http://adiguba.developpez.com
C'est édité ;)

a++
Permalien 27/08/2009 @ 21:43

Vous devez être identifié pour poster un commentaire.

Liste des blogs

Rechercher

<  Février 2010  >
Lun Mar Mer Jeu Ven Sam Dim
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28

Syndiquez ce blog XML

Articles :

Commentaires :

Vos questions techniques : forum d'entraide Blogs - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Hébergement - Participez - Copyright © 2000-2010 www.developpez.com - Legal informations.