 |
 |
7) Polymorphisme |
|
 |
|
Texte original |
 |
Traducteur : Jérome DANNOVILLE |
|
 |
|
Ce chapitre contient 4 pages
1
2
3
4
|
|
|
 |
 |
 |
 |
 |
 |
|
 |
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
|
 |
 |
 |
7: Polymorphism
|
 |
7: Polymorphisme
|
 |
 |
 |
Polymorphism is the third
essential feature of an object-oriented programming language, after data
abstraction and inheritance.
|
 |
Le polymorphisme est la troisième caractéristique essentielle
d'un langage de programmation orienté objet, après l'abstraction et l'héritage.
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
Upcasting revisited
|
 |
Upcasting
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
You also saw a problem arise, which is
embodied in the following:
|
 |
On avait vu le problème reprit ci-dessous apparaître:
|
 |
 |
 |
//: 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 } } ///:~
|
 |
//: 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 } } ///:~
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
Forgetting the object type
|
 |
Pourquoi utiliser l'upcast?
|
 |
 |
 |
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:
|
 |
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]:
|
 |
 |
 |
//: 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); } } ///:~
|
 |
//: 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); } } ///:~
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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?
|
 |
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?
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
The twist
|
 |
The twist
|
 |
 |
 |
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:
|
 |
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():
|
 |
 |
 |
public static void tune(Instrument i) { // ... i.play(Note.MIDDLE_C); }
|
 |
public static void tune(Instrument i) { // ... i.play(Note.MIDDLE_C); }
|
 |
 |
 |
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.
|
 |
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">
|
 |
 |
 |
Method-call binding
|
 |
Liaison de l'appel de méthode
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
Producing the right behavior
|
 |
Produire le bon comportement
|
 |
 |
 |
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.”
|
 |
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. »
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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:
|
 |
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 :
|
 |
 |
 |
|
 |
|
 |
 |
 |
The upcast could occur in a statement as
simple as:
|
 |
l'upcast pourrait se produire dans une instruction aussi simple
que :
|
 |
 |
 |
Shape s = new Circle();
|
 |
Shape s = new Circle();
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
Suppose you call one of the base-class methods (that have been overridden in the derived classes):
|
 |
Supposons que vous appeliez une des méthode de la classe de base qui a été
redéfinie dans les classes dérivées :
|
 |
 |
 |
s.draw();
|
 |
s.draw();
|
 |
 |
 |
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).
|
 |
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).
|
 |
 |
 |
The following example puts it a slightly different way:
|
 |
L'exemple suivant le montre de manière légèrement
différente :
|
 |
 |
 |
//: 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(); } } ///:~
|
 |
//: 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(); } } ///:~
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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:
|
 |
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 :
|
 |
 |
 |
Circle.draw() Triangle.draw() Circle.draw() Circle.draw() Circle.draw() Square.draw() Triangle.draw() Square.draw() Square.draw()
|
 |
Circle.draw() Triangle.draw() Circle.draw() Circle.draw() Circle.draw() Square.draw() Triangle.draw() Square.draw() Square.draw()
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
Extensibility
|
 |
Extensibilité
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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:
|
 |
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 :
|
 |
 |
 |
|
 |
|
 |
 |
 |
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:
|
 |
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 :
|
 |
 |
 |
//: 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); } } ///:~
|
 |
//: 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); } } ///:~
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |