 |
 |
1) Introduction sur les « objets » |
|
 |
|
Texte original |
 |
Traducteur : Jérome QUELIN |
|
 |
|
 |
 |
 |
 |
 |
 |
|
 |
|
 |
 |
 |
A type does more than describe the
constraints on a set of objects; it also has a relationship with other types.
Two types can have characteristics and behaviors in common, but one type may
contain more characteristics than another and may also handle more messages (or
handle them differently). Inheritance expresses this similarity between types
using the concept of base types and
derived types. A base type contains
all of the characteristics and behaviors that are shared among the types derived
from it. You create a base type to represent the core of your ideas about some
objects in your system. From the base type, you derive other types to express
the different ways that this core can be realized.
|
 |
Un type fait plus que décrire des contraintes sur un ensemble
d'objets ; il a aussi des relations avec d'autres types. Deux types peuvent avoir des
caractéristiques et des comportements en commun, mais l'un des deux peut avoir plus de
caractéristiques que l'autre et peut aussi réagir à plus de messages (ou y
réagir de manière différente). L'héritage exprime cette
similarité entre les types en introduisant le concept de types de base et de types
dérivés. Un type de base contient toutes les caractéristiques et comportements
partagés entre les types dérivés. Un type de base est créé pour
représenter le coeur de certains objets du système. De ce type de base, on
dérive d'autres types pour exprimer les différentes manières existantes pour
réaliser ce coeur.
|
 |
 |
 |
For example, a trash-recycling machine
sorts pieces of trash. The base type is “trash,” and each piece of
trash has a weight, a value, and so on, and can be shredded, melted, or
decomposed. From this, more specific types of trash are derived that may have
additional characteristics (a bottle has a color) or behaviors (an aluminum can
may be crushed, a steel can is magnetic). In addition, some behaviors may be
different (the value of paper depends on its type and condition). Using
inheritance, you can build a type hierarchy that expresses the problem
you’re trying to solve in terms of its types.
|
 |
Prenons l'exemple d'une machine de recyclage qui trie les détritus.
Le type de base serait « détritus », caractérisé par un
poids, une valeur, etc.. et peut être concassé, fondu, ou décomposé. A
partir de ce type de base, sont dérivés des types de détritus plus
spécifiques qui peuvent avoir des caractéristiques supplémentaires (une
bouteille a une couleur) ou des actions additionnelles (une canette peut être
découpée, un container d'acier est magnétique). De plus, des comportements
peuvent être différents (la valeur du papier dépend de son type et de son
état général). En utilisant l'héritage, on peut bâtir une
hiérarchie qui exprime le problème avec ses propres termes.
|
 |
 |
 |
A second example is the classic
“shape” example, perhaps used in a
computer-aided design system or game simulation. The base type is
“shape,” and each shape has a size, a color, a position, and so on.
Each shape can be drawn, erased, moved, colored, etc. From this, specific types
of shapes are derived (inherited): circle, square, triangle, and so on, each of
which may have additional characteristics and behaviors. Certain shapes can be
flipped, for example. Some behaviors may be different, such as when you want to
calculate the area of a shape. The type hierarchy embodies both the similarities
and differences between the shapes.
|
 |
Un autre exemple classique : les « formes
géométriques », utilisées entre autres dans les systèmes
d'aide à la conception ou dans les jeux vidéos. Le type de base est la
« forme géométrique », et chaque forme a une taille, une
couleur, une position, etc... Chaque forme peut être dessinée, effacée,
déplacée, peinte, etc... A partir de ce type de base, des types spécifiques
sont dérivés (hérités) : des cercles, des carrés, des
triangles et autres, chacun avec des caractéristiques et des comportements additionnels
(certaines figures peuvent être inversées par exemple). Certains comportements peuvent
être différents, par exemple quand on veut calculer l'aire de la forme. La
hiérarchie des types révèle à la fois les similarités et les
différences entre les formes.
|
 |
 |
 |
|
 |
|
 |
 |
 |
Casting the solution in the same terms as
the problem is tremendously beneficial because you don’t need a lot of
intermediate models to get from a description of the problem to a description of
the solution. With objects, the type hierarchy is the primary model, so you go
directly from the description of the system in the real world to the description
of the system in code. Indeed, one of the difficulties people have with
object-oriented design is that it’s too simple to get from the beginning
to the end. A mind trained to look for complex solutions is often stumped by
this simplicity at first.
|
 |
Représenter la solution avec les mêmes termes que ceux du
problème est extraordinairement bénéfique car on n'a pas besoin de
modèles intermédiaires pour passer de la description du problème à la
description de la solution. Avec les objets, la hiérarchie de types est le modèle
primaire, on passe donc du système dans le monde réel directement au système
du code. En fait, l'une des difficultés à laquelle les gens se trouvent
confrontés lors de la conception orientée objet est que c'est trop simple de passer
du début à la fin. Les esprits habitués à des solutions
compliquées sont toujours stupéfaits par cette simplicité.
|
 |
 |
 |
When you inherit from an existing type,
you create a new type. This new type contains not only all the members of the
existing type (although the private ones are hidden away and
inaccessible), but more important, it duplicates the interface of the base
class. That is, all the messages you can send to objects of the base class you
can also send to objects of the derived class. Since we know the type of a class
by the messages we can send to it, this means that the derived class is the
same type as the base class. In the previous example, “a circle is a
shape.” This type equivalence via inheritance is one of the fundamental
gateways in understanding the meaning of object-oriented
programming.
|
 |
Quand on hérite d'un certain type, on crée un nouveau type.
Ce nouveau type non seulement contient tous les membres du type existant (bien que les membres
privés soient cachés et inaccessibles), mais plus important, il duplique aussi
l'interface de la classe de la base. Autrement dit, tous les messages acceptés par les
objets de la classe de base seront acceptés par les objets de la classe
dérivée. Comme on connaît le type de la classe par les messages qu'on peut lui
envoyer, cela veut dire que la classe dérivée est du même type que la
classe de base. Dans l'exemple précédent, « un cercle est une forme
». Cette équivalence de type via l'héritage est l'une des notions fondamentales
dans la compréhension de la programmation orientée objet.
|
 |
 |
 |
Since both the base class and derived
class have the same interface, there must be some implementation to go along
with that interface. That is, there must be some code to execute when an object
receives a particular message. If you simply inherit a class and don’t do
anything else, the methods from the base-class interface come right along into
the derived class. That means objects of the derived class have not only the
same type, they also have the same behavior, which isn’t particularly
interesting.
|
 |
Comme la classe de base et la classe dérivée ont toutes les
deux la même interface, certaines implémentations accompagnent cette interface. C'est
à dire qu'il doit y avoir du code à exécuter quand un objet reçoit un
message particulier. Si on ne fait qu'hériter une classe sans rien lui rajouter, les
méthodes de l'interface de la classe de base sont importées dans la classe
dérivée. Cela veut dire que les objets de la classe dérivée n'ont pas
seulement le même type, ils ont aussi le même comportement, ce qui n'est pas
particulièrement intéressant.
|
 |
 |
 |
You have two ways to differentiate your
new derived class from the original base class. The first is quite
straightforward: You simply add brand new functions to the derived class. These
new functions are not part of the base class interface. This means that the base
class simply didn’t do as much as you wanted it to, so you added more
functions. This simple and primitive use for
inheritance is, at times, the
perfect solution to your problem. However, you should look closely for the
possibility that your base class might also need these additional functions.
This process of discovery and iteration of your design happens regularly in
object-oriented programming.
|
 |
Il y a deux façons de différencier la nouvelle classe
dérivée de la classe de base originale. La première est relativement
directe : il suffit d'ajouter de nouvelles fonctions à la classe dérivée.
Ces nouvelles fonctions ne font pas partie de la classe parent. Cela veut dire que la classe de
base n'était pas assez complète pour ce qu'on voulait en faire, on a donc
ajouté de nouvelles fonctions. Cet usage simple de l'héritage se
révèle souvent être une solution idéale. Cependant, il faut tout de
même vérifier s'il ne serait pas souhaitable d'intégrer ces fonctions dans la
classe de base qui pourrait aussi en avoir l'usage. Ce processus de découverte et
d'itération dans la conception est fréquent dans la programmation orientée
objet.
|
 |
 |
 |
|
 |
|
 |
 |
 |
Although inheritance may sometimes imply
(especially in Java, where the keyword that indicates inheritance is
extends) that you are going to add new functions to the interface,
that’s not necessarily true. The second and more important way to
differentiate your new class is to change the behavior of an existing
base-class function. This is referred to as
overriding that
function.
|
 |
Bien que l'héritage puisse parfois impliquer (spécialement en
Java, où le mot clef qui indique l'héritage est extends) que de
nouvelles fonctions vont être ajoutées à l'interface, ce n'est pas toujours
vrai. La seconde et plus importante manière de différencier la nouvelle classe est de
changer le comportement d'une des fonctions existantes de la superclasse. Cela
s'appelle redéfinir cette fonction.
|
 |
 |
 |
|
 |
|
 |
 |
 |
To override a function, you simply create
a new definition for the function in the derived class. You’re saying,
“I’m using the same interface function here, but I want it to do
something different for my new
type.”
|
 |
Pour redéfinir une fonction, il suffit de créer une nouvelle
définition pour la fonction dans la classe dérivée. C'est comme dire :
« j'utilise la même interface ici, mais je la traite d'une manière
différente dans ce nouveau type ».
|
 |
 |
 |
Is-a vs. is-like-a relationships
|
 |
Les relations est-un vs. est-comme-un
|
 |
 |
 |
There’s a certain debate that can
occur about inheritance: Should inheritance override only base-class
functions (and not add new member functions that aren’t in the base
class)? This would mean that the derived type is exactly the same type as
the base class since it has exactly the same interface. As a result, you can
exactly substitute an object of the derived class for an object of the base
class. This can be thought of as pure substitution,
and it’s often referred to as the substitution
principle. In a sense, this is the ideal way to treat inheritance. We often
refer to the relationship between the base class and derived classes in this
case as an is-a relationship, because you can say “a circle is
a shape.” A test for inheritance is to determine whether you can state
the is-a relationship about the classes and have it make sense.
|
 |
Un certain débat est récurrent à propos de
l'héritage : l'héritage ne devrait-il pas seulement redéfinir
les fonctions de la classe de base (et ne pas ajouter de nouvelles fonctions membres qui ne font
pas partie de la superclasse) ? Cela voudrait dire que le type dérivé serait
exactement le même que celui de la classe de base puisqu'il aurait exactement la
même interface. Avec comme conséquence logique le fait qu'on puisse exactement
substituer un objet de la classe dérivée à un objet de la classe de base. On
fait souvent référence à cette substitution pure sous le nom
de principe de substitution. Dans un sens, c'est la manière
idéale de traiter l'héritage. La relation entre la classe de base et la classe
dérivée dans ce cas est une relation est-un, parce qu'on peut dire «
un cercle est une forme ». Un test pour l'héritage est de
déterminer si la relation est-un entre les deux classes considérées a un
sens.
|
 |
 |
 |
There are times when you must add new
interface elements to a derived type, thus extending the interface and creating
a new type. The new type can still be substituted for the base type, but the
substitution isn’t perfect because your new functions are not accessible
from the base type. This can be described as an
is-like-a[6]
relationship; the new type has the interface of the old type but it also
contains other functions, so you can’t really say it’s exactly the
same. For example, consider an air conditioner. Suppose your house is wired with
all the controls for cooling; that is, it has an interface that allows you to
control cooling. Imagine that the air conditioner breaks down and you replace it
with a heat pump, which can both heat and cool. The heat pump is-like-an
air conditioner, but it can do more. Because the control system of your house is
designed only to control cooling, it is restricted to communication with the
cooling part of the new object. The interface of the new object has been
extended, and the existing system doesn’t know about anything except the
original interface.
|
 |
Mais parfois il est nécessaire d'ajouter de nouveaux
éléments à l'interface d'un type dérivé, et donc étendre
l'interface et créer un nouveau type. Le nouveau type peut toujours être
substitué au type de base, mais la substitution n'est plus parfaite parce que les nouvelles
fonctions ne sont pas accessibles à partir de la classe parent. On appelle cette relation
une relation est-comme-un [6]; le nouveau type
dispose de l'interface de l'ancien type mais il contient aussi d'autres fonctions, on ne peut donc
pas réellement dire que ce soient exactement les mêmes. Prenons le cas d'un
système de climatisation. Supposons que notre maison dispose des tuyaux et des
systèmes de contrôle pour le refroidissement, autrement dit elle dispose d'une
interface qui nous permet de contrôler le refroidissement. Imaginons que le système de
climatisation tombe en panne et qu'on le remplace par une pompe à chaleur, qui peut à
la fois chauffer et refroidir. La pompe à chaleur est-comme-un système de
climatisation, mais il peut faire plus de choses. Parce que le système de contrôle n'a
été conçu que pour contrôler le refroidissement, il en est restreint
à ne communiquer qu'avec la partie refroidissement du nouvel objet. L'interface du nouvel
objet a été étendue, mais le système existant ne connaît rien qui
ne soit dans l'interface originale.
|
 |
 |
 |
|
 |
|
 |
 |
 |
Of course, once you see this design it
becomes clear that the base class “cooling system” is not general
enough, and should be renamed to “temperature control system” so
that it can also include heating—at which point the substitution principle
will work. However, the diagram above is an example of what can happen in design
and in the real world.
|
 |
Bien sûr, quand on voit cette modélisation, il est clair que
la classe de base « Système de refroidissement » n'est pas assez
générale, et devrait être renommée en « Système de
contrôle de température » afin de pouvoir inclure le chauffage - auquel cas
le principe de substitution marcherait. Cependant, le diagramme ci-dessus est un exemple de ce qui
peut arriver dans le monde réel.
|
 |
 |
 |
When you see the substitution principle
it’s easy to feel like this approach (pure substitution) is the only way
to do things, and in fact it is nice if your design works out that way.
But you’ll find that there are times when it’s equally clear that
you must add new functions to the interface of a derived class. With inspection
both cases should be reasonably
obvious.
|
 |
Quand on considère le principe de substitution, il est tentant de se
dire que cette approche (la substitution pure) est la seule manière correcte de
modéliser, et de fait c'est appréciable si la conception fonctionne ainsi.
Mais dans certains cas il est tout aussi clair qu'il faut ajouter de nouvelles fonctions à
l'interface d'une classe dérivée. En examinant le problème, les deux cas
deviennent relativement évidents.
|
 |
 |
 |
Interchangeable objects with polymorphism
|
 |
Polymorphisme : des objets interchangeables
|
 |
 |
 |
When dealing with type hierarchies, you
often want to treat an object not as the specific type that it is, but instead
as its base type. This allows you to write code that doesn’t depend on
specific types. In the shape example, functions manipulate generic shapes
without respect to whether they’re circles, squares, triangles, or some
shape that hasn’t even been defined yet. All shapes can be drawn, erased,
and moved, so these functions simply send a message to a shape object; they
don’t worry about how the object copes with the message.
|
 |
Il arrive qu'on veuille traiter un objet non en tant qu'objet du type
spécifique qu'il est, mais en tant qu'objet de son type de base. Cela permet d'écrire
du code indépendant des types spécifiques. Dans l'exemple de la forme
géométrique, les fonctions manipulent des formes génériques sans se
soucier de savoir si ce sont des cercles, des carrés, des triangles ou même des formes
non encore définies. Toutes les formes peuvent être dessinées, effacées,
et déplacées, donc ces fonctions envoient simplement un message à un objet
forme, elles ne se soucient pas de la manière dont l'objet traite le message.
|
 |
 |
 |
Such code is unaffected by the addition
of new types, and adding new types is the most common way to extend an
object-oriented program to handle new situations. For example, you can derive a
new subtype of shape called pentagon without modifying the functions that
deal only with generic shapes. This ability to extend a program easily by
deriving new subtypes is important because it greatly improves designs while
reducing the cost of software maintenance.
|
 |
Un tel code n'est pas affecté par l'addition de nouveaux types, et
ajouter de nouveaux types est la façon la plus commune d'étendre un programme
orienté objet pour traiter de nouvelles situations. Par exemple, on peut dériver un
nouveau type de forme appelé pentagone sans modifier les fonctions qui traitent des formes
génériques. Cette capacité à étendre facilement un programme en
dérivant de nouveaux sous-types est important car il améliore considérablement
la conception tout en réduisant le coût de maintenance.
|
 |
 |
 |
There’s a problem, however, with
attempting to treat derived-type objects as their generic base types (circles as
shapes, bicycles as vehicles, cormorants as birds, etc.). If a function is going
to tell a generic shape to draw itself, or a generic vehicle to steer, or a
generic bird to move, the compiler cannot know at compile-time precisely what
piece of code will be executed. That’s the whole point—when the
message is sent, the programmer doesn’t want to know what piece of
code will be executed; the draw function can be applied equally to a circle, a
square, or a triangle, and the object will execute the proper code depending on
its specific type. If you don’t have to know what piece of code will be
executed, then when you add a new subtype, the code it executes can be different
without requiring changes to the function call. Therefore, the compiler cannot
know precisely what piece of code is executed, so what does it do? For example,
in the following diagram the BirdController object just works with
generic Bird objects, and does not know what exact type they are. This is
convenient from BirdController’s perspective because it
doesn’t have to write special code to determine the exact type of
Bird it’s working with, or that Bird’s behavior. So
how does it happen that, when move( ) is called while ignoring the
specific type of Bird, the right behavior will occur (a Goose
runs, flies, or swims, and a Penguin runs or swims)?
|
 |
Un problème se pose cependant en voulant traiter les types
dérivés comme leur type de base générique (les cercles comme des formes
géométriques, les vélos comme des véhicules, les cormorans comme des
oiseaux, etc...). Si une fonction demande à une forme générique de se
dessiner, ou à un véhicule générique de tourner, ou à un oiseau
générique de se déplacer, le compilateur ne peut savoir
précisément lors de la phase de compilation quelle portion de code sera
exécutée. C'est d'ailleurs le point crucial : quand le message est
envoyé, le programmeur ne veut pas savoir quelle portion de code sera
exécutée ; la fonction dessiner peut être appliquée aussi bien
à un cercle qu'à un carré ou un triangle, et l'objet va exécuter le bon
code suivant son type spécifique. Si on n'a pas besoin de savoir quelle portion de code est
exécutée, alors le code exécuté lorsque on ajoute un nouveau sous-type
peut être différent sans exiger de modification dans l'appel de la fonction. Le
compilateur ne peut donc précisément savoir quelle partie de code sera
exécutée, donc que va-t-il faire ? Par exemple, dans le diagramme suivant,
l'objet Contrôleur d'oiseaux travaille seulement avec des objets
Oiseaux génériques, et ne sait pas de quel type ils sont. Cela est
pratique du point de vue de Contrôleur d'oiseaux car il n'a pas besoin
d'écrire du code spécifique pour déterminer le type exact
d'Oiseau avec lequel il travaille, ou le comportement de cet
Oiseau. Comment se fait-il donc que, lorsque bouger() est
appelé tout en ignorant le type spécifique de l'Oiseau, on obtienne
le bon comportement (une Oie court, vole ou nage, et un Pingouin
court ou nage) ?
|
 |
 |
 |
|
 |
|
 |
 |
 |
The answer is the primary twist in
object-oriented programming: the compiler cannot make a function call in the
traditional sense. The function call generated by a non-OOP compiler causes what
is called early binding, a
term you may not have heard before because you’ve never thought about it
any other way. It means the compiler generates a call to a specific function
name, and the linker resolves this call to the absolute address of the code to
be executed. In OOP, the program cannot determine the address of the code until
run-time, so some other scheme is necessary when a message is sent to a generic
object.
|
 |
La réponse constitue l'astuce fondamentale de la programmation
orientée objet : le compilateur ne peut faire un appel de fonction au sens traditionnel
du terme. Un appel de fonction généré par un compilateur non orienté
objet crée ce qu'on appelle une association prédéfinie, un terme
que vous n'avez sans doute jamais entendu auparavant car vous ne pensiez pas qu'on puisse faire
autrement. En d'autres termes, le compilateur génère un appel à un nom de
fonction spécifique, et l'éditeur de liens résout cet appel à l'adresse
absolue du code à exécuter. En POO, le programme ne peut déterminer l'adresse
du code avant la phase d'exécution, un autre mécanisme est donc nécessaire
quand un message est envoyé à un objet générique.
|
 |
 |
 |
To solve the problem, object-oriented
languages use the concept of late
binding. When you send a message to an object, the code being called
isn’t determined until run-time. The compiler does ensure that the
function exists and performs type checking on the arguments and return value (a
language in which this isn’t true is called
weakly typed), but it
doesn’t know the exact code to execute.
|
 |
Pour résoudre ce problème, les langages orientés objet
utilisent le concept d'association tardive. Quand un objet reçoit un message, le
code appelé n'est pas déterminé avant l'exécution. Le compilateur
s'assure que la fonction existe et vérifie le type des arguments et de la valeur de retour
(un langage omettant ces vérifications est dit faiblement typé), mais
il ne sait pas exactement quel est le code à exécuter.
|
 |
 |
 |
To perform late binding, Java uses a
special bit of code in lieu of the absolute call. This code calculates the
address of the function body, using information stored in the object (this
process is covered in great detail in Chapter 7). Thus, each object can behave
differently according to the contents of that special bit of code. When you send
a message to an object, the object actually does figure out what to do with that
message.
|
 |
Pour créer une association tardive, Java utilise une portion
spéciale de code en lieu et place de l'appel absolu. Ce code calcule l'adresse du corps de
la fonction, en utilisant des informations stockées dans l'objet (ce mécanisme est
couvert plus en détails dans le Chapitre 7). Ainsi, chaque objet peut se comporter
différemment suivant le contenu de cette portion spéciale de code. Quand un objet
reçoit un message, l'objet sait quoi faire de ce message.
|
 |
 |
 |
In some languages (C++, in particular)
you must explicitly state that you want a function to have the flexibility of
late-binding properties. In these languages, by default, member functions are
not dynamically bound. This caused problems, so in Java dynamic binding
is the default and you don’t need to remember to add any extra keywords in
order to get polymorphism.
|
 |
Dans certains langages (en particulier le C++), il faut préciser
explicitement qu'on souhaite bénéficier de la flexibilité de l'association
tardive pour une fonction. Dans ces langages, les fonctions membres ne sont pas
liées dynamiquement par défaut. Cela pose des problèmes, donc en Java
l'association dynamique est le défaut et aucun mot clef supplémentaire n'est requis
pour bénéficier du polymorphisme.
|
 |
 |
 |
Consider the shape example. The family of
classes (all based on the same uniform interface) was diagrammed earlier in this
chapter. To demonstrate polymorphism, we want to write a single piece of code
that ignores the specific details of type and talks only to the base class. That
code is decoupled from type-specific information,
and thus is simpler to write and easier to understand. And, if a new
type—a Hexagon, for example—is added through inheritance, the
code you write will work just as well for the new type of Shape as it did
on the existing types. Thus, the program is extensible.
|
 |
Reprenons l'exemple de la forme géométrique. Le diagramme de
la hiérarchie des classes (toutes basées sur la même interface) se trouve plus
haut dans ce chapitre. Pour illustrer le polymorphisme, écrivons un bout de code qui ignore
les détails spécifiques du type et parle uniquement à la classe de base. Ce
code est déconnecté des informations spécifiques au type, donc
plus facile à écrire et à comprendre. Et si un nouveau type - un
Hexagone, par exemple - est ajouté grâce à l'héritage,
le code continuera de fonctionner aussi bien pour ce nouveau type de Forme qu'il
le faisait avec les types existants. Le programme est donc extensible.
|
 |
 |
 |
If you write a method in Java (as you
will soon learn how to do):
|
 |
Si nous écrivons une méthode en Java (comme vous allez
bientôt apprendre à le faire) :
|
 |
 |
 |
void doStuff(Shape s) { s.erase(); // ... s.draw(); }
|
 |
void faireQuelqueChose(Forme f) { f.effacer(); // ... f.dessiner(); }
|
 |
 |
 |
This function speaks to any Shape,
so it is independent of the specific type of object that it’s drawing and
erasing. If in some other part of the program we use the doStuff( )
function:
|
 |
Cette fonction s'adresse à n'importe quelle Forme,
elle est donc indépendante du type spécifique de l'objet qu'elle dessine et efface.
Si nous utilisons ailleurs dans le programme cette fonction
faireQuelqueChose() :
|
 |
 |
 |
Circle c = new Circle(); Triangle t = new Triangle(); Line l = new Line(); doStuff(c); doStuff(t); doStuff(l);
|
 |
Cercle c = new Cercle(); Triangle t = new Triangle(); Ligne l = new Ligne(); faireQuelqueChose(c); faireQuelqueChose(t); faireQuelqueChose(l);
|
 |
 |
 |
The calls to doStuff( )
automatically work correctly, regardless of the exact type of the object.
|
 |
Les appels à faireQuelqueChose() fonctionnent
correctement, sans se préoccuper du type exact de l'objet.
|
 |
 |
 |
This is actually a pretty amazing trick.
Consider the line:
|
 |
En fait c'est une manière de faire très
élégante. Considérons la ligne :
|
 |
 |
 |
doStuff(c);
|
 |
faireQuelqueChose(c);
|
 |
 |
 |
What’s happening here is that a Circle is being passed into a function that’s expecting a Shape. Since a Circle is a Shape it can be treated as one by doStuff( ). That is, any message that doStuff( ) can send to a Shape, a Circle can accept. So it is a completely safe and logical thing to do.
|
 |
Un Cercle est ici passé à une fonction qui
attend une Forme. Comme un Cercle est-une
Forme, il peut être traité comme tel par
faireQuelqueChose(). C'est à dire qu'un Cercle peut
accepter tous les messages que faireQuelqueChose() pourrait envoyer à une
forme. C'est donc une façon parfaitement logique et sûre de faire.
|
 |
 |
 |
We call this process of treating a derived type as though it were its base type upcasting. The name cast is used in the sense of casting into a mold and the up comes from the way the inheritance diagram is typically arranged, with the base type at the top and the derived classes fanning out downward. Thus, casting to a base type is moving up the inheritance diagram: “upcasting.”
|
 |
Traiter un type dérivé comme s'il était son type de
base est appelé transtypage ascendant, surtypage ou
généralisation (upcasting). L'adjectif ascendant vient du fait que dans
un diagramme d'héritage typique, le type de base est
représenté en haut, les classes dérivées s'y rattachant par le bas.
Ainsi, changer un type vers son type de base revient à remonter dans le diagramme
d'héritage : transtypage « ascendant ».
|
 |
 |
 |
|
 |
|
 |
 |
 |
An object-oriented program contains some upcasting somewhere, because that’s how you decouple yourself from knowing about the exact type you’re working with. Look at the code in doStuff( ):
|
 |
Un programme orienté objet contient obligatoirement des transtypages
ascendants, car c'est de cette manière que le type spécifique de l'objet peut
être délibérément ignoré. Examinons le code de
faireQuelqueChose() :
|
 |
 |
 |
s.erase(); // ... s.draw();
|
 |
f.effacer(); // ... f.dessiner();
|
 |
 |
 |
Notice that it doesn’t say
“If you’re a Circle, do this, if you’re a
Square, do that, etc.” If you write that kind of code, which checks
for all the possible types that a Shape can actually be, it’s messy
and you need to change it every time you add a new kind of Shape. Here,
you just say “You’re a shape, I know you can erase( )
and draw( ) yourself, do it, and take care of the details
correctly.”
|
 |
Remarquez qu'il ne dit pas « Si tu es un
Cercle, fais ceci, si tu es un Carré, fais cela,
etc... ». Ce genre de code qui vérifie tous les types possibles que peut prendre
une Forme est confus et il faut le changer à chaque extension de la classe
Forme. Ici, il suffit de dire : « Tu es une forme
géométrique, je sais que tu peux te dessiner() et
t'effacer(), alors fais-le et occupe-toi des détails spécifiques
».
|
 |
 |
 |
What’s impressive about the code in
doStuff( ) is that, somehow, the right thing happens. Calling
draw( ) for Circle causes different code to be executed than
when calling draw( ) for a Square or a Line, but when
the draw( ) message is sent to an anonymous Shape, the
correct behavior occurs based on the actual type of the Shape. This is
amazing because, as mentioned earlier, when the Java compiler is compiling the
code for doStuff( ), it cannot know exactly what types it is dealing
with. So ordinarily, you’d expect it to end up calling the version of
erase( ) and draw( ) for the base class Shape,
and not for the specific Circle, Square, or Line. And yet
the right thing happens because of polymorphism. The compiler and run-time
system handle the details; all you need to know is that it happens, and more
important how to design with it. When you send a message to an object, the
object will do the right thing, even when upcasting is
involved.
|
 |
Ce qui est impressionnant dans le code de
faireQuelqueChose(), c'est que tout fonctionne comme on le souhaite. Appeler
dessiner() pour un Cercle exécute une portion de code
différente de celle exécutée lorsqu'on appelle dessiner()
pour un Carré ou une Ligne, mais lorsque le message
dessiner() est envoyé à une Forme anonyme, on
obtient le comportement idoine basé sur le type réel de la Forme.
Cela est impressionnant dans la mesure où le compilateur Java ne sait pas à quel type
d'objet il a affaire lors de la compilation du code de faireQuelqueChose(). On
serait en droit de s'attendre à un appel aux versions dessiner() et
effacer() de la classe de base Forme, et non celles des classes
spécifiques Cercle, Carré et
Ligne. Mais quand on envoie un message à un objet, il fera ce qu'il a
à faire, même quand la généralisation est impliquée. C'est ce
qu'implique le polymorphisme. Le compilateur et le système d'exécution s'occupent des
détails, et c'est tout ce que vous avez besoin de savoir, en plus de savoir comment
modéliser avec.
|
 |
 |
 |
Abstract base classes and interfaces
|
 |
Classes de base abstraites et interfaces
|
 |
 |
 |
Often in a design, you want the base
class to present only an interface for its derived classes. That is, you
don’t want anyone to actually create an object of the base class, only to
upcast to it so that its interface can be used. This is accomplished by making
that class abstract using the abstract keyword. If anyone tries to
make an object of an abstract class, the compiler prevents them. This is
a tool to enforce a particular design.
|
 |
Dans une modélisation, il est souvent souhaitable qu'une classe de
base ne présente qu'une interface pour ses classes dérivées. C'est à
dire qu'on ne souhaite pas qu'il soit possible de créer un objet de cette classe de base,
mais seulement pouvoir surtyper jusqu'à elle pour pouvoir utiliser son interface. Cela est
possible en rendant cette classe abstraite en utilisant le mot clef
abstract. Le compilateur se plaindra si une tentative est faite de créer un
objet d'une classe définie comme abstract. C'est un outil utilisé
pour forcer une certain conception.
|
 |
 |
 |
You can also use the abstract
keyword to describe a method that hasn’t been implemented yet—as a
stub indicating “here is an interface function for all types inherited
from this class, but at this point I don’t have any implementation for
it.” An abstract method may be created only inside an abstract
class. When the class is inherited, that method must be implemented, or the
inheriting class becomes abstract as well. Creating an abstract
method allows you to put a method in an interface without being forced to
provide a possibly meaningless body of code for that method.
|
 |
Le mot clef abstract est aussi utilisé pour
décrire une méthode qui n'a pas encore été implémentée -
comme un panneau indiquant « voici une fonction de l'interface dont les types
dérivés ont hérité, mais actuellement je n'ai aucune
implémentation pour elle ». Une méthode abstract peut
seulement être créée au sein d'une classe abstract. Quand
cette classe est dérivée, cette méthode doit être
implémentée, ou la classe dérivée devient abstract
elle aussi. Créer une méthode abstract permet de l'inclure dans une
interface sans être obligé de fournir une portion de code éventuellement
dépourvue de sens pour cette méthode.
|
 |
 |
 |
The interface keyword takes the
concept of an abstract class one step further by preventing any function
definitions at all. The interface is a very handy and commonly used tool,
as it provides the perfect separation of interface and implementation. In
addition, you can combine many interfaces together, if you wish, whereas
inheriting from multiple regular classes or abstract classes is not
possible.
|
 |
Le mot clef interface pousse le concept de classe
abstract un cran plus loin en évitant toute définition de fonction.
Une interface est un outil très pratique et très largement
répandu, car il fournit une séparation parfaite entre l'interface et
l'implémentation. De plus, on peut combiner plusieurs interfaces, alors qu'hériter de
multiples classes normales ou abstraites est impossible.
|
 |
 |
 |
Object landscapes and lifetimes
|
 |
Environnement et durée de vie des objets
|
 |
 |
 |
Technically, OOP is just about abstract
data typing, inheritance, and polymorphism, but other issues can be at least as
important. The remainder of this section will cover these
issues.
|
 |
Techniquement, les spécificités de la programmation
orientée objet se résument au typage abstrait des données, à
l'héritage et au polymorphisme, mais d'autres particularités peuvent se
révéler aussi importantes. Le reste de cette section traite de ces
particularités.
|
 |
 |
 |
One of the most important factors is the
way objects are created and destroyed. Where is the data for an object and how
is the lifetime of the object controlled? There are different philosophies at
work here. C++ takes the approach that control of efficiency is the most
important issue, so it gives the programmer a choice. For maximum run-time
speed, the storage and lifetime can be determined while the program is being
written, by placing the objects on the stack (these are sometimes called
automatic or scoped variables) or in the static storage area. This
places a priority on the speed of storage allocation and release, and control of
these can be very valuable in some situations. However, you sacrifice
flexibility because you must know the exact quantity, lifetime, and type of
objects while you're writing the program. If you are trying to solve a more
general problem such as computer-aided design, warehouse management, or
air-traffic control, this is too restrictive.
|
 |
L'une des particularités les plus importantes est la façon
dont les objets sont créés et détruits. Où se trouvent les
données d'un objet et comment sa durée de vie est-elle contrôlée ?
Différentes philosophies existent. En C++, qui prône que l'efficacité est le
facteur le plus important, le programmeur a le choix. Pour une vitesse optimum à
l'exécution, le stockage et la durée de vie peuvent être
déterminés quand le programme est écrit, en plaçant les objets sur la
pile (ces variables sont parfois appelées automatiques ou de
portée) ou dans l'espace de stockage statique. La vitesse d'allocation et de
libération est dans ce cas prioritaire et leur contrôle peut être vraiment
appréciable dans certaines situations. Cependant, cela se fait aux dépends de la
flexibilité car il faut connaître la quantité exacte, la durée de vie et
le type des objets pendant qu'on écrit le programme. Si le problème à
résoudre est plus général, tel que de la modélisation assistée
par ordinateur, de la gestion d'entrepôts ou du contrôle de trafic aérien, cela
se révèle beaucoup trop restrictif.
|
 |
 |
 |
The second approach is to create objects
dynamically in a pool of memory called the heap. In this approach, you don't
know until run-time how many objects you need, what their lifetime is, or what
their exact type is. Those are determined at the spur of the moment while the
program is running. If you need a new object, you simply make it on the heap at
the point that you need it. Because the storage is managed dynamically, at
run-time, the amount of time required to allocate storage on the heap is
significantly longer than the time to create storage on the stack. (Creating
storage on the stack is often a single assembly instruction to move the stack
pointer down, and another to move it back up.) The dynamic approach makes the
generally logical assumption that objects tend to be complicated, so the extra
overhead of finding storage and releasing that storage will not have an
important impact on the creation of an object. In addition, the greater
flexibility is essential to solve the general programming
problem.
|
 |
La deuxième approche consiste à créer les objets
dynamiquement dans un pool de mémoire appelé le segment. Dans cette approche, le
nombre d'objets nécessaire n'est pas connu avant l'exécution, de même que leur
durée de vie ou leur type exact. Ces paramètres sont déterminés sur le
coup, au moment où le programme s'exécute. Si on a besoin d'un nouvel objet, il est
simplement créé dans le segment au moment où on en a besoin. Comme le stockage
est géré de manière dynamique lors de l'exécution, le temps de
traitement requis pour allouer de la place dans le segment est plus important que le temps mis pour
stocker sur la pile (stocker sur la pile se résume souvent à une instruction
assembleur pour déplacer le pointeur de pile vers le bas, et une autre pour le
redéplacer vers le haut). L'approche dynamique fait la supposition
généralement justifiée que les objets ont tendance à être
compliqués, et que le surcoût de temps dû à la recherche d'une place de
stockage et à sa libération n'aura pas d'impact significatif sur la création
d'un objet. De plus, la plus grande flexibilité qui en résulte est essentielle pour
résoudre le problème modélisé par le programme.
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |