Chapitre 2 : La programmation orientée objet

Le but de cet article est de présenter de façon précise et concise les notions du langage Java relatives à la programmation orientée objet. En clair, nous parlerons de l'encapsulation, l'héritage, la surcharge des méthodes, la conversion des types, l'instanciation. Sans oublier le couplage et la cohésion.

3 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Plusieurs parmi nous (je suis le premier) ont appris le langage Java en rassemblant des bouts de code dans le but de réaliser un logiciel (ce que l'on a coutume d'appeler formation sur le tas). Quoique cela a toujours fini par donner un résultat, on ne saurait cependant nier son inconvénient majeur : à la fin on ne sait jamais comment les choses fonctionnent au fond. Ce qui peut avoir pour conséquences : la mauvaise utilisation des types d'objets et de la mémoire qui parfois occasionne des pertes en temps énormes suite à une mauvaise approche lors du débogage.

À la suite du premier chapitre qui traitait des énumérations, des classes, des interfaces, des règles sur les identifiants et du standard JavaBean, ce chapitre (qui est le deuxième de la suite le mémo du certifier Java SE 6) va s'attarder sur le concept de programmation orientée objet en prenant le soin d'élucider les mécanismes qui gravitent tout au tour. Comme mécanismes, nous parlerons de : l'encapsulation, l'héritage, la surcharge des méthodes, la conversion des types, l'instanciation, du couplage et de la cohésion. À l'issue de cette présentation, cinq exercices corrigés sont mis à la disposition du lecteur pour des besoins d'évaluation personnelle. Je conseille vivement que ce test soit effectué avant et après la lecture de l'article.

II. Encapsulation

L'encapsulation est un mécanisme permettant de masquer l'implémentation des objets tout en les rendant accessibles à travers un certain nombre de services ou interfaces. Du fait qu'elle permette de définir les interfaces de manipulation de données au sein d'un objet, l'encapsulation constitue un gage pour l'intégrité des données.

  • L'encapsulation favorise la maintenabilité, la flexibilité et l'extensibilité des applications. Pour la mettre en œuvre, il faut :
  • Grâce aux méthodes d'accès (les getters et les setters), il vous sera possible de modifier votre code sans avoir besoin de faire des ajustements supplémentaires sur les classes qui l'utilisent.
  • Les variables locales résident dans le stack.
  1. Faire précéder les variables d'instance du mot-clé protected ou private ;
  2. Définir des accesseurs public et obliger les objets extérieurs à modifier ou à accéder aux variables d'instance uniquement à travers ses accesseurs. Il ne faut pas que les objets extérieurs puissent directement accéder aux variables d'instance ;
  3. Pour les méthodes d'accès aux variables d'instance, il faut utiliser les conventions de nommage JavaBeans (setXxx and getXxx ).

III. L'héritage , Is-A, Has-A

  • instanceof retourne true si la variable de référence à tester est du même type que la classe utilisée pour faire la comparaison.
 
Sélectionnez
Test t = new Test(); // t est la variable de référence
if(t instanceof Object) //Object est la classe pour la comparaison
  System.out.println("OK");
//le résultat est OK
  • Toutes les classes Java sont des sous-classes de la classe Object. Excepté la classe Object elle-même. C'est pour cette raison que toutes les classes en Java contiendront toujours les méthodes equals(), clone(), notify(), wait()…
  • L'utilisation de l'héritage favorise :
    1. la réutilisation du code ;
    2. la mise en œuvre du polymorphisme.
  • Dans le monde de l'Orienté Objet (OO), le concept de “Is-A” est basé sur l'héritage ou des classes ou l'implémentation des interfaces. Et cela est matérialisé par les mots-clés : extends pour l'héritage des classes et implements pour l'implémentation des interfaces.
  • La relation “Has-A” est basée sur l'utilisation plutôt que l'héritage. Une classe A “Has-A” B si la classe A possède une référence vers une instance de la classe B. Dans l'exemple ci-dessous, on dira que Voiture “Has-A” Roue et Rav4 “Is-A” Voiture.
 
Sélectionnez
public class Voiture {
   Roue roue;
}

class Roue {
}
class Rav4 extends Voiture {
}
  • La relation “Has-A” permet d'élaborer des classes en suivant de bonnes pratiques de l'orienté objet. Car cela évite d'avoir des classes monolithiques qui contiennent tout. Cela facilite aussi la réutilisation du code.

IV. La réécriture et la surcharge des méthodes

IV-A. La réécriture des méthodes

Lorsqu'on hérite d'une classe, on hérite aussi de son comportement et de ses caractéristiques. Il peut donc arriver pour une raison ou une autre de vouloir changer le mode opératoire d'une méthode. Alors, pour remplacer l'implémentation de la méthode héritée, on va procéder à une réécriture.

  • À chaque fois que vous avez une classe qui étend une autre, vous avez la possibilité de réécrire des méthodes, à moins que ces méthodes soient final, private ou static.
  • La réécriture d'une méthode ne doit pas avoir un modificateur d'accès plus restrictif que celui de la méthode réécrite.
 
Sélectionnez
public class Toyota extends Voiture{

    //erreur de compilation le modificateur doit être public      
    protected void rouler(){} 

    public void accelerer(){}//OK 

    //erreur de compilation le modificateur doit être public, default ou protected   
    private void freiner(){}

    protected void virer(){}//OK
}

class Voiture {
    public void rouler(){}      
    private void accelerer(){}    
    void freiner(){}
    void virer(){}
}
  • Les règles concernant la réécriture des méthodes :
    1. La liste des arguments de la réécriture doit être exactement la même que celle de la méthode réécrite. Sinon il s'agira d'une surcharge au lieu d'une réécriture ;
    2. Le type de retour doit être le même ou un sous-type du type déclaré dans la méthode réécrite ;
 
Sélectionnez
class Fabriquant {
  public Voiture fabriquer(){ return new Voiture();}
}
class Toyota extends Fabriquant{
  public Rav4 fabriquer(){ return new Rav4(); }
}
class Mazda extends Fabriquant {
 public Runner fabriquer(){ return new Runner(); }
}
class Voiture{}
class Rav4 extends Voiture {}
class Runner extends Voiture {}
    1. Le modificateur d'accès ne doit pas être plus restrictif ;
    2. On parle de réécriture de méthode uniquement lorsqu'il y a héritage ;
    3. La réécriture d'une méthode peut lever de nouvelles exceptions du type “Runtime Exception” même si la méthode réécrite ne l'a pas déclarée ;
 
Sélectionnez
class Fabriquant {
  public Voiture fabriquer(){ 
    return new Voiture();
  }
}
class Toyota extends Fabriquant{
  public Voiture fabriquer() throws RuntimeException{ 
    return new Voiture();
  }
  //une erreur de compilation se produit si on décommente la méthode
  //public Voiture fabriquer() throws Exception{ return new Voiture();}
}
class Voiture{}
    1. La réécriture d'une méthode ne doit pas lever une exception du type “checked exception” qui ne soit pas déclarée par la méthode réécrite ou qui n'ait pas de parent parmi les exceptions déclarées au niveau de la méthode réécrite ;*
 
Sélectionnez
class Toyota extends Voiture{
  public void rouler() throws ArithmeticException,
     NullPointerException{} //OK exception du type RuntimeException
          
 /*
   erreur de compilation, exception du type checked 
   exception non déclarée dans la super-méthode
 */
 public void accelerer() throws ClassNotFoundException { }
          
  // OK, sous-classe de la classe Exception
 public void virer() throws ClassNotFoundException{}
}
    
class Voiture{
  public void rouler(){}
  public void accelerer(){}
  public void virer() throws Exception{}
 }
    1. La réécriture d'une méthode peut lever moins d'exceptions que la super-méthode. Ceci s'explique par le fait qu'il est possible de traiter dans la réécriture des exceptions levées au niveau de la super-méthode ;
 
Sélectionnez
class Toyota extends Voiture{
   public void rouler() {} //ok          
}
            
Public class Voiture{
  public void rouler() throws IOException, ParseException{}
}
    1. On ne peut pas réécrire une méthode final ;
    2. On ne peut pas réécrire une méthode static ;
    3. Si une méthode ne peut pas être héritée (par exemple les méthodes private), elle ne peut non plus être réécrite.
 
Sélectionnez
class Animal{ 
  public void eat(){System.out.println("Animal");}
} 
class Horse extends Animal { 
  public void eat(){
    super.eat();
    System.out.println("Horse");
  }
}
  • En Java, il n'existe pas de réécriture de variable. Ainsi, il est possible de déclarer des variables de même nom dans une sous-classe et dans la classe parent sans qu'il y ait interférence.
 
Sélectionnez
class Animal{ 
  int age = 6;  
} 
class Horse extends Animal { 
  int age = 10;
  public void eat(){
    Horse h = new Horse();
    Animal a = new Horse();
    System.out.println("ageH : "+h.age+" ageA : "+a.age);
  }
}
//ageH : 10 ageA : 6

IV-B. La surcharge des méthodes

Il s'agit d'un mécanisme permettant d'avoir au sein d'une classe, des méthodes/constructeurs ayant le même nom avec des arguments de type différent. Ainsi, on dira qu'il y a surcharge de méthodes/constructeurs dans une classe, lorsque deux méthodes au moins posséderont le même nom avec chacune des particularités au niveau du type d'argument pris en paramètre.

 
Sélectionnez
class Horse { 

  public void eat(){ }
  public void eat(String food){ }
  public void eat(int age){ }
}
  • Les règles concernant la surcharge des méthodes :
    1. les méthodes qui participent à la surcharge ne doivent pas avoir les mêmes listes d'arguments ;
 
Sélectionnez
class Horse { 
  public void eat(){ }
  public void eat(String food){ }
  public void eat(String water){ }//erreur à la compilation
  public void eat(String food, int age){ }
}
    1. les méthodes d'une surcharge peuvent avoir des types retour distincts ;
    2. les méthodes d'une surcharge peuvent avoir des modificateurs d'accès différents ;
    3. les méthodes d'une surcharge peuvent déclarer des exceptions de façon indépendante ;
    4. une méthode peut être surchargée dans la même classe ou dans une sous-classe. Ceci dit, deux méthodes de même nom, mais contenues dans différentes classes peuvent toujours être considérées comme surchargées.
 
Sélectionnez
class Animal{ 
  public void eat(){}
} 
class Horse extends Animal { 
  
  protected Horse eat(int age) throws Exception{ }
  public int eat(String food){ }
  private Animal eat(String food, int age) throws RuntimeException{ }
}

IV-C. Appel des méthodes surchargées

  • L'appel d'une méthode surchargée est fonction du type de la référence et non du type de l'objet.
 
Sélectionnez
class Animal{} class Horse extends Animal {}
class TheAnimals {
  void eat(Animal a){System.out.printl("Animal");}
  void eat(Horse h){System.out.printl("Horse");}
  public static void main(String[] args){
    TheAnimals us = new TheAnimals();
    Animal an = new Animal();
    Animal anref = new Horse();
    Horse hor = new Horse();
    us.eat(an);//affiche Animal
    us.eat(anref);//affiche Animal
    us.eat(hor);//affiche Horse
  }
}
  • Même si à l'exécution anref est de type Horse, le choix de la méthode surchargée à appeler est déterminé à la compilation en fonction du type de référence qui est Animal pour le cas d'espèce.
  • De manière générale, l'appel d'une version de méthodes réécrite est déterminé à l'exécution de l'application en fonction du type des objets. Tandis que, l'appel d'une version de méthodes surchargée est déterminé à la compilation en fonction du type de référence des paramètres.
  • Quelques différences entre la réécriture et la surcharge :
  Méthode surchargée Méthode réécrite
La liste des arguments Doit changer Ne doit pas changer
Le type de retour Peut changer Doit être le même ou un sous-type du type déclaré dans la méthode à réécrire
Exceptions Peuvent changer Peuvent réduire ou éliminer les exceptions. Ne doivent pas lever de nouvelles exceptions de type « checked»
Modificateur d'accès Peut changer Il ne doit pas être plus restrictif que celui de la méthode à réécrire
Invocation Elle s'appuie sur le type de référence à la compilation Elle s'appuie sur le type d'objet à l'exécution

V. Conversion et accès aux variables d'une classe

V-A. Conversion des types de variables (casting)

 
Sélectionnez
class Animal {} 
class Dog extends Animal { 
    void playDead(){} 
}
If you tried :
   Animal a = new Dog(); 
   a.playDead(); //you will get a compile error : cannot find symbol
But if you do something like : 
   Dog d = (Dog)animal; //this code compiles! when you try to run it we'll get an exception ClassCastException
   Animal a = new Animal(); 
   String s = (String)animal;//animal can never be a String we'll get compile error like  inconvertible types.

V-B. Méthodes et variables statiques

  • La variable static d'une classe est partagée par toutes les instances de cette classe.
 
Sélectionnez
public class Animal {

  static int speed;

  public static void main(String[] args){
     Animal an1 = new Animal();
     an1.speed++;
     Animal an2 = new Animal();
     an2.speed++;
     Animal an3 = new Animal();
     an3.speed++;
     System.out.println("speed : "+speed);//le resultat est 3
  }
}
  • Une méthode static ne peut pas directement accéder aux variables ou aux méthodes non static.
  • On peut accéder aux objets (variables et méthodes) static d'une classe en faisant : «nom_de_la_classe ».«nom_objet_static» ou «référence_objet».«nom_objet_static».
 
Sélectionnez
public class Animal {

  static int speed;
  public static void main(String[] args){
     //ces deux appellations sont identiques
     System.out.println(Animal.speed);//nom_de_la_classe.nom_objet_static 
     Animal an = new Animal();
     System.out.println(an.speed);//référence_objet.nom_objet_static
  }
}
  • Une méthode static ne peut pas être réécrite dans une sous-classe, mais elle peut être redéfinie.
 
Sélectionnez
public class Animal {

  public static void manger(){
     System.out.println("Animal");
  }
  public static void main(String[] args){
     Animal a = new Animal();
     a.manger();//imprime Animal
     a = new Lion();
     a.manger();//imprime Animal au lieu de Lion
     Lion l = new Lion();
     l.manger();//imprime Lion
  }
}

class Lion extends Animal {
  public static void manger(){
     System.out.println("Lion");
  }
}

VI. Implémentation des interfaces

  • Afin d'implémenter une interface, une classe non abstract doit :
    1. fournir une implémentation concrète pour toutes les méthodes déclarées dans l'interface ;
    2. respecter toutes les règles de la réécriture des méthodes.
  • Une classe non abstract peut aussi implémenter une interface. Dans ce cas, la classe n'est pas obligée de fournir une implémentation concrète des méthodes de l'interface.
  • Une classe peut implémenter plus d'une interface mais ne peut étendre qu'une seule classe.
  • Une interface peut étendre une autre interface, mais ne peut rien implémenter d'autre.
 
Sélectionnez
interface Voiture {

  public void rouler();
}

interface Toyota extends Voiture {}

abstract class Rav4 implements Toyota {}

public class Rav4NewDesign extends Rav4 {
  public void rouler(){  }
}

VII. Type retour des méthodes

VII-A. Déclaration des types de retour

  • Pour une méthode surchargée, il n'y a pas de restriction sur le type de retour. Rappelez-vous que pour surcharger une méthode, vous avez seulement besoin de changer les arguments qu'elle prend en paramètre. Ainsi, une méthode surchargée est complètement différente de ses consœurs.
  • Pour une méthode réécrite, il est possible de changer le type de retour tant que le nouveau type est un sous-type du type retourné par la méthode réécrite.

VII-B. Le retour des valeurs

  • Il y a principalement cinq règles à respecter pour le retour des valeurs :
    1. On ne peut retourner null que pour des méthodes qui retournent des types d'objet (à l'opposé des primitives) ;
 
Sélectionnez
class Voiture {

  public int vitesse(){ //elle retourne une primitive
     return null;//erreur de compilation
  }
  
  public Integer kilometrage(){//elle retourne un objet (un wrapper)
     return null;//ok
  }
}
    1. Il est légal de retourner des tableaux d'objets ou de primitives ;
    2. Pour une méthode qui retourne une primitive, il est possible de retourner une valeur ou une variable qui peut implicitement être convertie au type de retour déclaré. Dans le cas contraire, il faudra d'abord convertir la valeur à retourner ;
 
Sélectionnez
class Voiture {
  public int vitesse(){
     char a = 'a';
     return a;
  }
  
  public int vitesse(double speed){
     return (int) speed;
  }
}
    1. On ne doit rien retourner pour une méthode qui retourne le type void ;
 
Sélectionnez
class Voiture {

//les deux méthodes sont correctes
  public void vitesse(){
     return;
  }  
  public void vitesse(double speed){}
}
    1. Pour une méthode qui retourne un objet, il est possible de retourner tout objet qui peut implicitement est converti au type de retour déclaré ;
 
Sélectionnez
interface Voiture {}

class Toyota implements Voiture{}

class Mazda implements Voiture{}

public class Fabrication {

  public Voiture fabriqueToyota(){
     Toyota t = new Toyota();
     return t;
  }
  
  public Voiture fabriqueMazda(){
     Mazda m = new Mazda();
     return m;
  }
}

VIII. Les constructeurs

En Programmation Orientée Objet (POO), un constructeur est une méthode « spéciale » d'une classe qui permet de créer une instance d'un objet (instancier), d'allouer la mémoire nécessaire à l'objet et d'initialiser ses attributs.

VIII-A. Les constructeurs et l'instanciation

  • Toute classe, y compris les classes abstract doivent avoir au moins un constructeur.
  • Un constructeur n'a aucun type retour et son nom doit exactement être le même que celui de la classe qui l'abrite.
 
Sélectionnez
public class Animal {
   
   public Animal(){}//ok
   public Animal(Object obj){}//ok
   public animal(){}//non,il faut respecter la casse
   public void Animal(){}//c'est une méthode légale pas un constructeur
}
  • Les constructeurs sont généralement utilisés pour initialiser les variables d'instance.
  • Un constructeur est invoqué à l'exécution lorsqu'on utilise le mot-clé new pour l'instance d'un objet : Animal a = new Animal();.
  • Le déroulement du processus de création d'une instance.
  • L'instanciation d'un objet en Java suit plusieurs étapes. Pour comprendre ce processus, considérons la création d'un objet Bmwv de l'exemple suivant :
 
Sélectionnez
class Voiture {
   public Voiture(){
      System.out.print("-Voiture-");
   }
}
public class Bmwv extends Voiture{

   public Bmwv(){
      System.out.print("-Bmwv-");
   }   
   
   public static void main(String... args){
       new Bmwv();
   }
}
//resultat : -Voiture--Bmwv-
    1. le constructeur Bmwv() est invoqué ;
    2. avant que la méthode print() ne soit exécutée, le constructeur Voiture() est invoqué. Ici, il est important de noter que, tout constructeur invoque toujours le constructeur de la super-classe de façon explicite à travers la méthode super() ou de façon implicite, à moins que le constructeur n'invoque un constructeur surchargé de la même classe. Dans notre cas, Voiture est la super-classe de Bmwv ;
    3. le constructeur de la classe Object est invoqué car, la classe Object est la super-classe de toutes les classes ;
    4. les variables d'instance de la classe Object sont initialisées ;
    5. le constructeur de la classe Object est entièrement exécuté ;
    6. les variables d'instance de la classe Voiture sont initialisées (assignation des valeurs par défaut) ;
    7. le constructeur de la classe Voiture est entièrement exécuté. Dans notre cas, c'est ici que sera imprimé le texte «-Voiture-» ;
    8. les variables d'instance de la classe Bmwv sont initialisées (assignation des valeurs par défaut) ;
    9. enfin, le constructeur de la classe Bmwv est entièrement exécuté. Et c'est ici que sera imprimé le texte «-Bmwv-». D'où le résultat.
    1. un constructeur peut avoir n'importe quel modificateur d'accès, y compris private (sauf que, dans le cas d'un modificateur d'accès private, la classe doit fournir une méthode static ou une variable qui permettra l'accès à l'instance créée au sein de la classe) ;
    2. le nom du constructeur doit être le même que celui de la classe qui l'abrite ;
    3. un constructeur ne doit pas avoir de type retour ;
    4. il est possible d'avoir une méthode ayant le même nom que la classe, mais cela ne fait pas de cette méthode un constructeur.
 
Sélectionnez
class Voiture {
   public Voiture(){
      System.out.print("-Voiture-");
      super();//erreur de compilation, la méthode super doit venir en premier
   }
   
   public Voiture(int i){
   // la méthode super(); sera insérée à la compilation à ce niveau
      System.out.print("-Voiture-");      
   }
   public Voiture(long i){
     this(2);//OK, appel du constructeur qui prend un int en paramètre      
   }
}
  • La méthode super() peut être appelée sans argument, mais il est aussi possible de lui passer des arguments en fonction des constructeurs définis dans la super-classe.
  • Un constructeur sans argument n'est pas forcément un constructeur par défaut (constructeur généré par le compilateur).
  • Il est impossible d'accéder à une méthode de classe ou une variable d'instance avant l'appel du super-constructeur.
 
Sélectionnez
class Voiture {
   public Voiture(int i)  }   
}
public class Bmwv extends Voiture{
   int speed;
   static int value;
   
   public Bmwv(){
      super(speed);//erreur de compilation, speed est une variable d'instance
      //super(value); OK car value est une variable static
   }     
}
  • Uniquement des variables static et des méthodes static peuvent être passées en paramètre des appels à super() ou this().
  • Les classes abstraites possèdent aussi des constructeurs et ces constructeurs sont toujours appelés lors du processus d'instanciation des sous-classes.
  • Une interface ne possède pas de constructeur.
  • L'unique façon de faire appel à un constructeur dans un autre c'est d'utiliser la méthode this(). En d'autres termes, il n'est pas possible d'appeler un constructeur comme suit :
 
Sélectionnez
class Voiture {
   public Voiture(){}
   public Voiture(int i){     
      Voiture();//erreur de compilation
    //il faut faire : this();
   }   
}

VIII-B. Conditions de création d'un constructeur par défaut

  • Le compilateur crée le constructeur par défaut dans une classe lorsque ladite classe ne comporte aucun constructeur à la compilation.
  • Les caractéristiques d'un constructeur par défaut :
    1. il possède le même modificateur d'accès que la classe qui le contient ;
    2. il n'a aucun argument ;
    3. il fait appel au super-constructeur sans argument (super()).
 
Sélectionnez
public class Animal {
   protected Animal(){//ce n'est pas un constructeur par défaut
      super();
   } 
}
public class Animal {
   public Animal(){//c'est un constructeur par défaut
      super();
   } 
}
  • Si une super-classe ne contient que des constructeurs avec argument, vous êtes obligé de faire appel à la méthode super() en lui passant les arguments nécessaires dans tous les constructeurs des sous-classes.
 
Sélectionnez
class Voiture {
   public Voiture(int speed){}
}
public class Toyota extends Voiture {
   public Toyota(){} //erreur de compilation
   public Toyota(int speed){
      super(0);//OK
   }
}
  • Si une super-classe ne contient que des constructeurs avec arguments, vous êtes obligé de définir des constructeurs dans toutes ses sous-classes et chaque constructeur devra faire appel à la méthode super() en lui passant les arguments nécessaires.
 
Sélectionnez
class Voiture {
   public Voiture(int speed){}
}
public class Toyota extends Voiture {
   //erreur de compilation
}

VIII-C. La surcharge des constructeurs

  • Surcharger un constructeur signifie créer plusieurs versions de constructeurs avec des arguments différents.
 
Sélectionnez
public class Voiture {
   public Voiture(){}
   public Voiture(short sieges){}
   public Voiture(int speed){}
}
  • Si vous voulez appeler un constructeur dans un autre, vous devez utiliser le mot-clé this.
 
Sélectionnez
class Animal {
String name;
Animal(String name){this.name = name}
Animal(){ this("My name");}//appel du premier constructeur
}
  • La première instruction dans un constructeur doit être l'appel de la méthode super() ou this(). Si aucune de ces instructions n'est insérée, le compilateur va insérer la méthode super() sans argument.
  • Un constructeur ne peut pas avoir à la fois un appel à super() et un appel à this(), car chacune de ses méthodes doit être la première instruction dans un constructeur.
  • Un compilateur ne peut pas insérer la méthode super() dans un constructeur qui contient déjà la méthode this().

IX. Le couplage et la cohésion

L'objectif d'une bonne conception est de faciliter la création, la maintenance et l'évolution d'une application. Pour ce faire, elle devra favoriser un couplage lâche entre les entités et une forte cohésion de chacune des entités.

IX-A. Le couplage

  • Le couplage ici permet d'exprimer le niveau de visibilité qu'une classe a sur une autre. Ainsi, deux classes seront fortement couplées si le changement d'une variable dans l'une nécessite impérativement un changement dans l'autre.
  • Si deux classes s'échangent des données uniquement à travers des interfaces alors les deux classes sont dites faiblement couplées. Ce qui est une bonne chose. Dans le cas d'espèce, la classe Voiture et la classe Roue sont faiblement couplées. Ainsi, il devient possible de modifier l'attribut diamètre ou poids dans la classe Roue sans toutefois perturber la relation Voiture-Roue.
 
Sélectionnez
class Voiture {
    Roue roue;
    public void montage(){
        roue = new Roue();
        roue.setDiametre(12.5);
        roue.setPoids(19);
    }
}

class Roue {
   private double poids;
   private double diametre;

    public double getDiametre() {
        return diametre;
    }

    public void setDiametre(double diametre) {
        this.diametre = diametre;
    }

    public double getPoids() {
        return poids;
    }

    public void setPoids(double poids) {
        this.poids = poids;
    }   
}
  • Si une classe A accède aux données d'une classe B sans passer par les interfaces de la classe B alors les classes A et B sont dites fortement couplées. Ce qui n'est pas une bonne chose pour une application.
 
Sélectionnez
class Voiture {
    Roue roue;
    public void montage(){
        roue = new Roue();
        roue.diametre = 12.5;
        roue.poids = 19;
    }
}

class Roue {
   public double poids;
   public double diametre;    
}

IX-B. La cohésion

  • Alors que le couplage concerne les interactions entre les classes, la cohésion se focalise sur la conception interne d'une classe.
  • La cohésion permet d'exprimer le niveau de conception d'une classe en tant qu'entité indépendante. Cela nécessite l'encapsulation des données et le masquage des informations.
  • Une classe ayant une forte cohésion est facilement maintenable et réutilisable.

X. Exercices

X-A. Corrigez les erreurs et trouvez le résultat

 
Sélectionnez
class Animale {
  public Animale(String nom) { System.out.print("B"); }  
}
public class Chien implements Animale {
  public Chien(String nom) { System.out.print("A"); }
  public static void main(String ... args) {
    new Chien("C");
    System.out.println(" "); }}

X-B. Corrigez les erreurs et trouvez le résultat

 
Sélectionnez
class Animale {
  private final void manger() { System.out.println("Animale"); }
}
public class Chien extends Animale {
  public final void manger() { System.out.println("Chien");  }
  public static void main(String [] args) { 
    new Chien().manger(); 
  }
}

X-C. Corrigez les erreurs et trouvez le résultat

 
Sélectionnez
class Animale { public void manger() { 
    System.out.println("Animale");} }
    
 class Chien extends Animale { 
    void manger() {System.out.println("Chien");} 
    void aboie() {System.out.println("Chien-aboie");}  
    } 

 class Courrir {
   public static void main(String [] args) {
     Animale a1 = new Chien();
     Animale a2 = new Chien();
     Chien c = new Chien();
        a1.manger();
        a1.manger();
      a1.aboie();
        c.manger();
      c.aboie();
 } }

X-D. Corrigez les erreurs et trouvez le résultat

 
Sélectionnez
public class Chien extends Animale { 
   public static String manger() { return "viande"; }
   public static void main(String args[]) {
     Chien c1 = new Chien();
     Animale a1 = new Chien();
     Animale a2 = new Animale();
     System.out.println(c1.manger() + " " + a1.manger() + " " + a2.manger());
   }
 }
 class Animale { public static String manger() { return "nourriture"; } }

X-E. Corrigez les erreurs et trouvez le résultat

 
Sélectionnez
class Animale { }
class Chien implements Animale { }
public class Foret {
   String s = "-";
   public static void main(String[] args) {
     Animale[] a = new Animale[2];
     Chien[] c = new Chien[2];
     chercher(a);
     chercher(c);
     chercher(7);
     System.out.println(s);
   }
   static void chercher(Animale[]... a)    { s += "1"; }
   static void chercher(Chien[]... b)    { s += "2"; }
   static void chercher(Chien[] b)       { s += "3"; }
   static void chercher(Object o)     { s += "4"; }
 }

XI. Correction des exercices

XI-A. Exercice 1

  1. Erreurs

On implémente une interface et on étend une classe. Il faut donc utiliser le mot-clé extends entre Chien et Animale

Lorsqu'une classe mère n'a pas de constructeur sans argument, la classe fille doit explicitement faire appel à la méthode super() dans tous ses constructeurs en lui passant les paramètres nécessaires. Dans notre cas, nous allons appeler la méthode super() avant l'instruction System.out.print("A"); en lui passant le nom de l'animal comme paramètre.

  1. Solution

Le résultat après correction des anomalies est : BA. Car, à l'instanciation de la classe Chien, le constructeur de la classe mère (Animale) sera entièrement exécuté avant celui de la classe fille (Chien).

XI-B. Exercice 2

  1. Erreurs

Il n'y a aucune erreur. Par contre, si la méthode manger () de la classe Animale était public, defaultou protected, un problème allait se poser parce que l'on ne peut réécrire une méthode final.

  1. Solution

La solution est : Chien

XI-C. Exercice 3

  1. Erreurs

La réécriture d'une méthode ne doit pas avoir un modificateur d'accès plus restrictif que celui de la méthode réécrite. Ici, la méthode manger() de la classe Chien doit avoir public comme modificateur d'accès .

L'appel d'une version de méthode réécrite est déterminé à l'exécution de l'application en fonction du type des objets et non du type de référence. Dans le cas présent, pour la variable a1 le type d'objet est Chien et le type de référence est Animale. Or la classe animale ne possède pas de méthode aboie(). D'où il faudra commenter l'instruction a1.aboie();

  1. Solution

La solution est :

Chien

Chien

Chien

Chien - aboie

XI-D. Exercice 4

  1. Erreurs

Il n'y a aucune erreur.

  1. Solution

La solution sera : viande nourriture nourriture. Il faut se rappeler que l'on ne peut pas réécrire une méthode static. Ceci dit, l'instruction a1.manger() fait appel à la méthode manger() de la classe Animale au lieu de la classe Chien qui est le type de l'objet.

XI-E. Exercice 5

  1. Erreurs

On implémente une interface et on étend une classe. Il faut donc utiliser le mot-clé extends entre Chien et Animale.

On ne peut accéder aux variables non-static dans une méthode static. Il faut donc déclarer la variable « s » comme static.

  1. Solution

Le résultat est : -434

Conclusion

Le prochain article traitera principalement des variables. On verra entre autres, la création, l'initialisation, le transtypage, le passage en paramètre et la suppression des variables. Mais avant de nous quitter je voudrais adresser mes remerciements à ClaudeLELOUPClaudeLELOUPetRobin56Robin56 pour la relecture orthographique, et à keulkeulkeulkeul pour ses propositions, ses encouragements et le temps consacré pour les diverses relectures.

Pour réaliser cet article, j'ai fait usage de deux livres que je trouve complémentaires et que je n'hésite pas à vous conseiller. Il s'agit de :

SCJP Sun Certified Programmer for Java 6 Study Guide de Kathy Sierra et Bert Bates ;

A Programmer's Guide to Java™ SCJP Certification Third Edition de Khalid A. Mughal et Rolf W. Rasmussen.

Suivez moi sur twitter

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.