t
t
t
t
t t 7) Polymorphisme
tttt
7) Polymorphisme
Texte original t Traducteur : Jérome DANNOVILLE
t
t
    
Ce chapitre contient 4 pages
1 2 3 4
\\\
t t t
t t t
t
05.07.01 - version 1.2 [Armel] :
- Ajout des tags de séparation des pages pour le site.
21.06.01 - Version 1.1 :
- Correction du code html,
   guillemets "texte" -> « texte »,
   texte: -> texte :,
   texte? -> texte ? [Armel].
14.06.01 - Version 1.0 :
- Relecture en cours [JeromeDanno].
Traducteur :
- Jérome DANNOVILLE
Texte original :
-Thinking in Java, 2nd edition, Revision 10
© 2000 by Bruce Eckel
t t t
7: Polymorphism t 7: Polymorphisme
t t t
Polymorphism is the third essential feature of an object-oriented programming language, after data abstraction and inheritance. t Le polymorphisme est la troisième caractéristique essentielle d'un langage de programmation orienté objet, après l'abstraction et l'héritage.
t t t
It provides another dimension of separation of interface from implementation, to decouple what from how. Polymorphism allows improved code organization and readability as well as the creation of extensible programs that can be “grown” not only during the original creation of the project but also when new features are desired. t Le polymorphisme fournit une autre dimension séparant la partie interface de l'implémentation qui permet de découpler le quoi du comment. Le polymorphisme améliore l'organisation du code et sa lisibilité de même qu'il permet la création de programmes extensible qui peuvent évoluer non seulement pendant la création initiale du projet mais également quand des fonctions nouvelles sont désirées.
t t t
Encapsulation creates new data types by combining characteristics and behaviors. Implementation hiding separates the interface from the implementation by making the details private. This sort of mechanical organization makes ready sense to someone with a procedural programming background. But polymorphism deals with decoupling in terms of types. In the last chapter, you saw how inheritance allows the treatment of an object as its own type or its base type. This ability is critical because it allows many types (derived from the same base type) to be treated as if they were one type, and a single piece of code to work on all those different types equally. The polymorphic method call allows one type to express its distinction from another, similar type, as long as they’re both derived from the same base type. This distinction is expressed through differences in behavior of the methods that you can call through the base class. t L'encapsulation crée de nouveaux types de données en combinant les caractéristiques et les comportements. Cacher la mise en œuvre permet de séparer l'interface de l'implémentation en mettant les détails privés [private]. Cette sorte d'organisation mécanique est bien comprise par ceux viennent de la programmation procédurale. Mais le polymorphisme s'occupe de découpler au niveau des types. Dans le chapitre précédant, nous avons vu comment l'héritage permet le traitement d'un objet comme son propre type ou son type de base. Cette capacité est critique car elle permet à beaucoup de types (dérivé d'un même type de base) d'être traités comme s'ils n'étaient qu'un type, et permet a un seul morceau de code de traiter sans distinction tous ces types différents. L'appel de méthode polymorphe permet à un type d'exprimer sa distinction par rapport à un autre, un type semblable, tant qu'ils dérivent tous les deux d'un même type de base. Cette distinction est exprimée à travers des différences de comportement des méthodes que vous pouvez appeler par la classe de base.
t t t
In this chapter, you’ll learn about polymorphism (also called dynamic binding or late binding or run-time binding) starting from the basics, with simple examples that strip away everything but the polymorphic behavior of the program. t Dans ce chapitre, vous allez comprendre le polymorphisme [également appelé en anglais dynamic binding ou late binding ou encore run-time binding] en commençant par les notions de base, avec des exemples simples qui évacuent tout ce qui ne concerne pas le comportement polymorphe du programme.
t t t

Upcasting revisited

t

Upcasting

t t t
In Chapter 6 you saw how an object can be used as its own type or as an object of its base type. Taking an object reference and treating it as a reference to its base type is called upcasting, because of the way inheritance trees are drawn with the base class at the top. t Dans le chapitre 6 nous avons vu qu'un objet peut être manipulé avec son propre type ou bien comme un objet de son type de base. Prendre la référence d'un objet et l'utiliser comme une référence sur le type de base est appelé upcasting (transtypage en français) , en raison du mode de représentation des arbres d'héritages avec la classe de base en haut.
t t t
You also saw a problem arise, which is embodied in the following: t On avait vu le problème reprit ci-dessous apparaître:
t t t
//: c07:music:Music.java
// Inheritance & upcasting.

class Note {
  private int value;
  private Note(int val) { value = val; }
  public static final Note
    MIDDLE_C = new Note(0),
    C_SHARP  = new Note(1),
    B_FLAT   = new Note(2);
} // Etc.

class Instrument {
  public void play(Note n) {
    System.out.println("Instrument.play()");
  }
}

// Wind objects are instruments
// because they have the same interface:
class Wind extends Instrument {
  // Redefine interface method:
  public void play(Note n) {
    System.out.println("Wind.play()");
  }
}

public class Music {
  public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
  }
  public static void main(String[] args) {
    Wind flute = new Wind();
    tune(flute); // Upcasting
  }
} ///:~
t
//: c07:music:Music.java
// Héritage & upcasting.
class Note {
  private int value;
  private Note(int val) { value = val; }
  public static final Note
    MIDDLE_C = new Note(0),
    C_SHARP  = new Note(1),
    B_FLAT   = new Note(2);
} // Etc.
class Instrument {
  public void play(Note n) {
    System.out.println("Instrument.play()");
  }
}

// Les objets Wind sont des instruments
// car ils ont la même interface:
class Wind extends Instrument {
  // Redéfinition de la méthode de l'interface:
  public void play(Note n) {
    System.out.println("Wind.play()");
  }
}

public class Music {
  public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
  }
  public static void main(String[] args) {
    Wind flute = new Wind();
    tune(flute); // Upcasting
  }
} ///:~
t t t
The method Music.tune( ) accepts an Instrument reference, but also anything derived from Instrument. In main( ), you can see this happening as a Wind reference is passed to tune( ), with no cast necessary. This is acceptable; the interface in Instrument must exist in Wind, because Wind is inherited from Instrument. Upcasting from Wind to Instrument may “narrow” that interface, but it cannot make it anything less than the full interface to Instrument. t La méthode Music.tune() accepte une référence sur un Instrument, mais également sur tout ce qui dérive de Instrument. Dans le main(), ceci se matérialise par une référence sur un objet Wind qui est passée à tune(), sans qu'un changement de type (un cast) soit nécessaire. Ceci est correct; l'interface dans Instrument doit exister dans Wind, car Wind hérite de la classe Instrument. Utiliser l'upcast de Wind vers Instrument peut « rétrécir » cette interface, mais au pire elle est réduite à l'interface complète d'Instrument.
t t t

Forgetting the object type

t

Pourquoi utiliser l'upcast?

t t t
This program might seem strange to you. Why should anyone intentionally forget the type of an object? This is what happens when you upcast, and it seems like it could be much more straightforward if tune( ) simply takes a Wind reference as its argument. This brings up an essential point: If you did that, you’d need to write a new tune( ) for every type of Instrument in your system. Suppose we follow this reasoning and add Stringed and Brass instruments: t Ce programme pourrait vous sembler étrange. Pourquoi donc oublier intentionnellement le type d'un objet? C'est ce qui arrive quand on fait un upcast, et il semble beaucoup plus naturel que tune() prenne tout simplement une référence sur Wind comme argument. Ceci introduit un point essentiel: en faisant ça, il faudrait écrire une nouvelle méthode tune() pour chaque type de Instrument du système. Supposons que l'on suive ce raisonnement et que l'on ajoute les instruments à cordes [Stringed] et les cuivres [Brass]:
t t t
//: c07:music2:Music2.java
// Overloading instead of upcasting.

class Note {
  private int value;
  private Note(int val) { value = val; }
  public static final Note
    MIDDLE_C = new Note(0),
    C_SHARP = new Note(1),
    B_FLAT = new Note(2);
} // Etc.

class Instrument {
  public void play(Note n) {
    System.out.println("Instrument.play()");
  }
}

class Wind extends Instrument {
  public void play(Note n) {
    System.out.println("Wind.play()");
  }
}

class Stringed extends Instrument {
  public void play(Note n) {
    System.out.println("Stringed.play()");
  }
}

class Brass extends Instrument {
  public void play(Note n) {
    System.out.println("Brass.play()");
  }
}

public class Music2 {
  public static void tune(Wind i) {
    i.play(Note.MIDDLE_C);
  }
  public static void tune(Stringed i) {
    i.play(Note.MIDDLE_C);
  }
  public static void tune(Brass i) {
    i.play(Note.MIDDLE_C);
  }
  public static void main(String[] args) {
    Wind flute = new Wind();
    Stringed violin = new Stringed();
    Brass frenchHorn = new Brass();
    tune(flute); // No upcasting
    tune(violin);
    tune(frenchHorn);
  }
} ///:~
t
//: c07:music2:Music2.java
// Surcharger plutôt que d'utiliser l'upcast.

class Note {
  private int value;
  private Note(int val) { value = val; }
  public static final Note
    MIDDLE_C = new Note(0),
    C_SHARP = new Note(1),
    B_FLAT = new Note(2);
} // Etc.

class Instrument {
  public void play(Note n) {
    System.out.println("Instrument.play()");
  }
}

class Wind extends Instrument {
  public void play(Note n) {
    System.out.println("Wind.play()");
  }
}

class Stringed extends Instrument {
  public void play(Note n) {
    System.out.println("Stringed.play()");
  }
}

class Brass extends Instrument {
  public void play(Note n) {
    System.out.println("Brass.play()");
  }
}

public class Music2 {
  public static void tune(Wind i) {
    i.play(Note.MIDDLE_C);
  }
  public static void tune(Stringed i) {
    i.play(Note.MIDDLE_C);
  }
  public static void tune(Brass i) {
    i.play(Note.MIDDLE_C);
  }
  public static void main(String[] args) {
    Wind flute = new Wind();
    Stringed violin = new Stringed();
    Brass frenchHorn = new Brass();
    tune(flute); // Pas d' upcast
    tune(violin);
    tune(frenchHorn);
  }
} ///:~
t t t
This works, but there’s a major drawback: You must write type-specific methods for each new Instrument class you add. This means more programming in the first place, but it also means that if you want to add a new method like tune( ) or a new type of Instrument, you’ve got a lot of work to do. Add the fact that the compiler won’t give you any error messages if you forget to overload one of your methods and the whole process of working with types becomes unmanageable. t Ceci fonctionne, mais avec un inconvénient majeur: il vous faut écrire des classes spécifique à chaque ajout d'une classe Instrument. Ceci implique davantage de programmation dans un premier temps, mais également beaucoup de travail à venir si l'on désire ajouter une nouvelle méthode comme tune() ou un nouveau type d' Instrument. Sans parler du compilateur qui est incapable de signaler l'oubli de surcharge de l'une de vos méthodes qui fait que toute cette construction utilisant les types devient assez compliquée.
t t t
Wouldn’t it be much nicer if you could just write a single method that takes the base class as its argument, and not any of the specific derived classes? That is, wouldn’t it be nice if you could forget that there are derived classes, and write your code to talk only to the base class? t Ne serait-il pas plus commode d'écrire une seule méthode qui prenne la name="Index673">classe de base en argument plutôt que toutes les classes dérivées spécifiques? Ou encore, ne serait-il pas agréable d'oublier qu'il y a des classes dérivées et d'écrire votre code en ne s'adressant qu'à la classe de base?
t t t
That’s exactly what polymorphism allows you to do. However, most programmers who come from a procedural programming background have a bit of trouble with the way polymorphism works. t C'est exactement ce que le polymorphisme vous permet de faire. Souvent, ceux qui viennent de la programmation procédurale sont déroutés par le mode de fonctionnement du polymorphisme.
t t t

The twist

t

The twist

t t t
The difficulty with Music.java can be seen by running the program. The output is Wind.play( ). This is clearly the desired output, but it doesn’t seem to make sense that it would work that way. Look at the tune( ) method: t L'ennui avec Music.java peut être visualisé en exécutant le programme. L'output est Wind.play(). C'est bien sûr le résultat attendu, mais il n'est pas évident de comprendre le fonctionnement.. Examinons la méthode tune():
t t t
  public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
}
t
  public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
  }
t t t
It receives an Instrument reference. So how can the compiler possibly know that this Instrument reference points to a Wind in this case and not a Brass or Stringed? The compiler can’t. To get a deeper understanding of the issue, it’s helpful to examine the subject of binding. t Elle prend une référence sur un Instrument en argument. Comment le compilateur peut-il donc deviner que cette référence sur un Instrument pointe dans le cas présent sur un objet Wind et non pas un objet Brass ou un objet Stringed? Hé bien il ne peut pas. Mieux vaut examiner le mécanisme d'association [binding] pour bien comprendre la question soulevée.name="_Toc312374042">
t t t

Method-call binding

t

Liaison de l'appel de méthode

t t t
Connecting a method call to a method body is called binding. When binding is performed before the program is run (by the compiler and linker, if there is one), it’s called early binding. You might not have heard the term before because it has never been an option with procedural languages. C compilers have only one kind of method call, and that’s early binding. t Raccorder un appel de méthode avec le corps de cette méthode est appelé association. Quand cette association est réalisée avant l'exécution du programme (par le compilateur et l'éditeur de lien, s'il y en a un), c’est de l’association prédéfinie. Vous ne devriez pas avoir déjà entendu ce terme auparavant car avec les langages procéduraux, c'est imposé . Les compilateurs C n'ont qu'une sorte d'appel de méthode, l'association prédéfinie.
t t t
The confusing part of the above program revolves around early binding because the compiler cannot know the correct method to call when it has only an Instrument reference. t Ce qui déroute dans le programme ci-dessus tourne autour de l'association prédéfinie car le compilateur ne peut pas connaître la bonne méthode a appeler lorsqu'il ne dispose que d'une référence sur Instrument.
t t t
The solution is called late binding, which means that the binding occurs at run-time based on the type of object. Late binding is also called dynamic binding or run-time binding. When a language implements late binding, there must be some mechanism to determine the type of the object at run-time and to call the appropriate method. That is, the compiler still doesn’t know the object type, but the method-call mechanism finds out and calls the correct method body. The late-binding mechanism varies from language to language, but you can imagine that some sort of type information must be installed in the objects. t La solution s'appelle l' association tardive, ce qui signifie que l'association est effectuée à l'exécution en se basant sur le type de l'objet. L'association tardive est également appelée association dynamique [dynamic binding ou run-time binding]. Quand un langage implémente l'association dynamique, un mécanisme doit être prévu pour déterminer le type de l'objet lors de l'exécution et pour appeler ainsi la méthode appropriée. Ce qui veut dire que le compilateur ne connaît toujours pas le type de l'objet, mais le mécanisme d'appel de méthode trouve et effectue l'appel vers le bon corps de méthode. Les mécanismes d'association tardive varient selon les langages, mais vous pouvez deviner que des informations relatives au type doivent être implantées dans les objets.
t t t
All method binding in Java uses late binding unless a method has been declared final. This means that ordinarily you don’t need to make any decisions about whether late binding will occur—it happens automatically. t Toutes les associations de méthode en Java utilisent l'association tardive à moins que l'on ait déclaré une méthode final. Cela signifie que d'habitude vous n'avez pas à vous préoccuper du déclenchement de l'association tardive, cela arrive automatiquement.
t t t
Why would you declare a method final? As noted in the last chapter, it prevents anyone from overriding that method. Perhaps more important, it effectively “turns off” dynamic binding, or rather it tells the compiler that dynamic binding isn’t necessary. This allows the compiler to generate slightly more efficient code for final method calls. However, in most cases it won’t make any overall performance difference in your program, so it’s best to only use final as a design decision, and not as an attempt to improve performance. t Pourquoi déclarer une méthode avec final? On a vu dans le chapitre précédant que cela empêche quelqu'un de redéfinir cette méthode. Peut-être plus important, cela « coupe » effectivement l'association dynamique, ou plutôt cela indique au compilateur que l'association dynamique n'est pas nécessaire. Le compilateur génère du code légèrement plus efficace pour les appels de méthodes spécifiés final. Cependant, dans la plupart des cas cela ne changera pas la performance globale de votre programme; mieux vaut utiliser final à la suite d'une décision de conception, et non pas comme tentative d'amélioration des performances.
t t t

Producing the right behavior

t

Produire le bon comportement

t t t
Once you know that all method binding in Java happens polymorphically via late binding, you can write your code to talk to the base class and know that all the derived-class cases will work correctly using the same code. Or to put it another way, you “send a message to an object and let the object figure out the right thing to do.” t Quand vous savez qu'en Java toute association de méthode se fait de manière polymorphe par l'association tardive, vous pouvez écrire votre code en vous adressant à la classe de base en sachant que tous les cas des classes dérivées fonctionneront correctement avec le même code. Dit autrement, vous « envoyez un message à un objet et laissez l'objet trouver le comportement adéquat. »
t t t
The classic example in OOP is the “shape” example. This is commonly used because it is easy to visualize, but unfortunately it can confuse novice programmers into thinking that OOP is just for graphics programming, which is of course not the case. t L'exemple classique utilisée en POO est celui de la forme [shape]. Cet exemple est généralement utilisé car il est facile à visualiser, mais peut malheureusement sous-entendre que la POO est cantonnée au domaine graphique, ce qui n'est bien sûr pas le cas.
t t t
The shape example has a base class called Shape and various derived types: Circle, Square, Triangle, etc. The reason the example works so well is that it’s easy to say “a circle is a type of shape” and be understood. The inheritance diagram shows the relationships: t Dans cet exemple il y a une classe de base appelée Shape et plusieurs types dérivés: Circle, Square, Triangle, etc. Cet exemple marche très bien car il est naturel de dire qu'un cercle est « une sorte de forme. » Le diagramme d'héritage montre les relations :
t t t
t Image
t t t
The upcast could occur in a statement as simple as: t l'upcast pourrait se produire dans une instruction aussi simple que :
t t t
Shape s = new Circle();
t
Shape s = new Circle();
t t t
Here, a Circle object is created
and the resulting reference is immediately assigned to a Shape, which
would seem to be an error (assigning one type to another); and yet it’s
fine because a Circle is a Shape by inheritance. So the
compiler agrees with the statement and doesn’t issue an error
message.
t On créé un objet Circle et la nouvelle référence est assignée à un Shape, ce qui semblerait être une erreur (assigner un type à un autre), mais qui est valide ici car un Cercle [Circle] est par héritage une sorte de forme [Shape]. Le compilateur vérifie la légalité de cette instruction et n'émet pas de message d'erreur.
t t t
Suppose you call one of the base-class
methods (that have been overridden in the derived classes):

t Supposons que vous appeliez une des méthode de la classe de base qui a été redéfinie dans les classes dérivées :
t t t
s.draw();
t
s.draw();
t t t
Again, you might expect that
Shape’s draw( ) is called because this is, after all, a
Shape reference—so how could the compiler know to do anything else?
And yet the proper Circle.draw( ) is called because of late binding
(polymorphism).
t De nouveau, vous pourriez vous attendre à ce que la méthode draw() de Shape soit appelée parce que c'est après tout une référence sur Shape. Alors comment le compilateur peut-il faire une autre liaison? Et malgré tout le bon Circle.draw() est appelé grâce à la liaison tardive (polymorphisme).
t t t
The following example puts it a slightly
different way:

t L'exemple suivant le montre de manière légèrement différente :
t t t
//: c07:Shapes.java
// Polymorphism in Java.

class Shape {
  void draw() {}
  void erase() {}
}

class Circle extends Shape {
  void draw() {
    System.out.println("Circle.draw()");
  }
  void erase() {
    System.out.println("Circle.erase()");
  }
}

class Square extends Shape {
  void draw() {
    System.out.println("Square.draw()");
  }
  void erase() {
    System.out.println("Square.erase()");
  }
}

class Triangle extends Shape {
  void draw() {
    System.out.println("Triangle.draw()");
  }
  void erase() {
    System.out.println("Triangle.erase()");
  }
}

public class Shapes {
  public static Shape randShape() {
    switch((int)(Math.random() * 3)) {
      default:
      case 0: return new Circle();
      case 1: return new Square();
      case 2: return new Triangle();
    }
  }
  public static void main(String[] args) {
    Shape[] s = new Shape[9];
    // Fill up the array with shapes:
    for(int i = 0; i < s.length; i++)
      s[i] = randShape();
    // Make polymorphic method calls:
    for(int i = 0; i < s.length; i++)
      s[i].draw();
  }
} ///:~
t
//: c07:Shapes.java
// Polymorphisme en Java.

class Shape {
  void draw() {}
  void erase() {}
}

class Circle extends Shape {
  void draw() {
    System.out.println("Circle.draw()");
  }
  void erase() {
    System.out.println("Circle.erase()");
  }
}

class Square extends Shape {
  void draw() {
    System.out.println("Square.draw()");
  }
  void erase() {
    System.out.println("Square.erase()");
  }
}

class Triangle extends Shape {
  void draw() {
    System.out.println("Triangle.draw()");
  }
  void erase() {
    System.out.println("Triangle.erase()");
  }
}

public class Shapes {
  public static Shape randShape() {
    switch((int)(Math.random() * 3)) {
      default:
      case 0: return new Circle();
      case 1: return new Square();
      case 2: return new Triangle();
    }
  }
  public static void main(String[] args) {
    Shape[] s = new Shape[9];
    // Remplissage du tableau avec des formes [shapes]:
    forint i = 0; i < s.length; i++)
      s[i] = randShape();
    // Appel polymorphe des méthodes:
    forint i = 0; i < s.length; i++)
      s[i].draw();
  }
} ///:~
t t t
The base class Shape establishes the common interface to anything inherited from Shape—that is, all shapes can be drawn and erased. The derived classes override these definitions to provide unique behavior for each specific type of shape. t La classe de base Shape établit l'interface commune pour tout ce qui hérite de Shape — C'est à dire, toutes les formes (shapes en anglais) peuvent être dessinées [draw] et effacées [erase]. Les classes dérivées redéfinissent ces méthodes pour fournir un comportement unique pour chaque type de forme spécifique.
t t t
The main class Shapes contains a static method randShape( ) that produces a reference to a randomly-selected Shape object each time you call it. Note that the upcasting happens in each of the return statements, which take a reference to a Circle, Square, or Triangle and sends it out of the method as the return type, Shape. So whenever you call this method you never get a chance to see what specific type it is, since you always get back a plain Shape reference. t La classe principale Shapes contient une méthode statique randShape () qui rend une référence sur un objet sélectionné de manière aléatoire à chaque appel. Remarquez que la généralisation se produit sur chaque instruction return, qui prend une référence sur un cercle [circle], un carré [square], ou un triangle et la retourne comme le type de retour de la méthode, en l'occurrence Shape. Ainsi à chaque appel de cette méthode vous ne pouvez pas voir quel type spécifique vous obtenez, puisque vous récupérez toujours une simple référence sur Shape.
t t t
main( ) contains an array of Shape references filled through calls to randShape( ). At this point you know you have Shapes, but you don’t know anything more specific than that (and neither does the compiler). However, when you step through this array and call draw( ) for each one, the correct type-specific behavior magically occurs, as you can see from one output example: t Le main() a un tableau de références sur Shape remplies par des appels a randShape(). Tout ce que l'on sait dans la première boucle c'est que l'on a des objets formes [Shapes], mais on ne sait rien de plus (pareil pour le compilateur). Cependant, quand vous parcourez ce tableau en appelant draw() pour chaque référence dans la seconde boucle, le bon comportement correspondant au type spécifique se produit comme par magie, comme vous pouvez le constater sur l'output de l'exemple :
t t t
Circle.draw()
Triangle.draw()
Circle.draw()
Circle.draw()
Circle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Square.draw()
t
Circle.draw()
Triangle.draw()
Circle.draw()
Circle.draw()
Circle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Square.draw()
t t t
Of course, since the shapes are all chosen randomly each time, your runs will have different results. The point of choosing the shapes randomly is to drive home the understanding that the compiler can have no special knowledge that allows it to make the correct calls at compile-time. All the calls to draw( ) are made through dynamic binding. t Comme toutes les formes sont choisies aléatoirement à chaque fois, vous obtiendrez bien sûr des résultats différents. L'intérêt de choisir les formes aléatoirement est d'illustrer le fait que le compilateur ne peut avoir aucune connaissance spéciale lui permettant de générer les appels corrects au moment de la compilation. Tous les appels à draw() sont réalisés par liaison dynamique.
t t t

Extensibility

t

Extensibilité

t t t
Now let’s return to the musical instrument example. Because of polymorphism, you can add as many new types as you want to the system without changing the tune( ) method. In a well-designed OOP program, most or all of your methods will follow the model of tune( ) and communicate only with the base-class interface. Such a program is extensible because you can add new functionality by inheriting new data types from the common base class. The methods that manipulate the base-class interface will not need to be changed at all to accommodate the new classes. t Revenons maintenant à l'exemple sur l'instrument de musique. En raison du polymorphisme, vous pouvez ajouter autant de nouveaux types que vous voulez dans le système sans changer la méthode tune(). Dans un programme orienté objet bien conçu, la plupart ou même toutes vos méthodes suivront le modèle de tune() et communiqueront seulement avec l'interface de la classe de base. Un tel programme est extensible parce que vous pouvez ajouter de nouvelles fonctionnalités en héritant de nouveaux types de données de la classe de base commune. Les méthodes qui utilisent l'interface de la classe de base n'auront pas besoin d'être retouchées pour intégrer de nouvelles classes.
t t t
Consider what happens if you take the instrument example and add more methods in the base class and a number of new classes. Here’s the diagram: t Regardez ce qui se produit dans l'exemple de l'instrument si vous ajoutez des méthodes dans la classe de base et un certain nombre de nouvelles classes. Voici le schéma :
t t t
t Image
t t t
All these new classes work correctly with the old, unchanged tune( ) method. Even if tune( ) is in a separate file and new methods are added to the interface of Instrument, tune( ) works correctly without recompilation. Here is the implementation of the above diagram: t Toutes ces nouvelles classes fonctionnent correctement avec la vieille méthode tune(), sans modification. Même si tune() est dans un fichier séparé et que de nouvelles méthodes sont ajoutées à l'interface de Instrument, tune() fonctionne correctement sans recompilation. Voici l'implémentation du diagramme présenté ci-dessus :
t t t
//: c07:music3:Music3.java
// An extensible program.
import java.util.*;

class Instrument {
  public void play() {
    System.out.println("Instrument.play()");
  }
  public String what() {
    return "Instrument";
  }
  public void adjust() {}
}

class Wind extends Instrument {
  public void play() {
    System.out.println("Wind.play()");
  }
  public String what() { return "Wind"; }
  public void adjust() {}
}

class Percussion extends Instrument {
  public void play() {
    System.out.println("Percussion.play()");
  }
  public String what() { return "Percussion"; }
  public void adjust() {}
}

class Stringed extends Instrument {
  public void play() {
    System.out.println("Stringed.play()");
  }
  public String what() { return "Stringed"; }
  public void adjust() {}
}

class Brass extends Wind {
  public void play() {
    System.out.println("Brass.play()");
  }
  public void adjust() {
    System.out.println("Brass.adjust()");
  }
}

class Woodwind extends Wind {
  public void play() {
    System.out.println("Woodwind.play()");
  }
  public String what() { return "Woodwind"; }
}

public class Music3 {
  // Doesn't care about type, so new types
  // added to the system still work right:
  static void tune(Instrument i) {
    // ...
    i.play();
  }
  static void tuneAll(Instrument[] e) {
    for(int i = 0; i < e.length; i++)
      tune(e[i]);
  }
  public static void main(String[] args) {
    Instrument[] orchestra = new Instrument[5];
    int i = 0;
    // Upcasting during addition to the array:
    orchestra[i++] = new Wind();
    orchestra[i++] = new Percussion();
    orchestra[i++] = new Stringed();
    orchestra[i++] = new Brass();
    orchestra[i++] = new Woodwind();
    tuneAll(orchestra);
  }
} ///:~
t
//: c07:music3:Music3.java
// Un programme extensible.
import java.util.*;

class Instrument {
  public void play() {
    System.out.println("Instrument.play()");
  }
  public String what() {
    return "Instrument";
  }
  public void adjust() {}
}

class Wind extends Instrument {
  public void play() {
    System.out.println("Wind.play()");
  }
  public String what() { return "Wind"; }
  public void adjust() {}
}

class Percussion extends Instrument {
  public void play() {
    System.out.println("Percussion.play()");
  }
  public String what() { return "Percussion"; }
  public void adjust() {}
}

class Stringed extends Instrument {
  public void play() {
    System.out.println("Stringed.play()");
  }
  public String what() { return "Stringed"; }
  public void adjust() {}
}

class Brass extends Wind {
  public void play() {
    System.out.println("Brass.play()");
  }
  public void adjust() {
    System.out.println("Brass.adjust()");
  }
}

class Woodwind extends Wind {
  public void play() {
    System.out.println("Woodwind.play()");
  }
  public String what() { return "Woodwind"; }
}

public class Music3 {
// Indépendants des types, ainsi les nouveaux types
  // ajoutés au système marchent toujours bien:
  static void tune(Instrument i) {
    // ...
    i.play();
  }
  static void tuneAll(Instrument[] e) {
    forint i = 0; i < e.length; i++)
      tune(e[i]);
  }
  public static void main(String[] args) {
    Instrument[] orchestra = new Instrument[5];
    int i = 0;
    // Upcasting pendant l'ajout au tableau:
    orchestra[i++] = new Wind();
    orchestra[i++] = new Percussion();
    orchestra[i++] = new Stringed();
    orchestra[i++] = new Brass();
    orchestra[i++] = new Woodwind();
    tuneAll(orchestra);
  }
} ///:~
t t t
t t t
t t
    
///
t t t
t
     
Sommaire Le site de Bruce Eckel