Más contenido relacionado
Similar a introduction introdaction introduction introdaction introduction introdaction (20)
introduction introdaction introduction introdaction introduction introdaction
- 1. Nous abordons aujourd'hui un volet très
important de votre apprentissage de la
programmation.
La programmation dite orientée objet.
Il s'agit d'une façon particulière de
programmer, qui
n'est d'ailleurs pas spécifique au
language C++, et qui
va donner à vos programmes un certain
nombre de
propriétés intéressantes en terme de
maintenabilité et de modularité.
À ce stade du cours, vous êtes censés
connaître un certain nombre d'éléments
fondamentaux relatifs à la programmation
tout court.
Vous savez par exemple exprimer
traitements en utilisant des structures de
contrôle, comme par exemple des boucles
conditionnelles, vous savez aussi
structurer un
minimum vos données, en utilisant par
exemple des tableaux, et vous
savez surtout modulariser vos programmes
en
utilisant la notion centrale de fonction.
Cet outillage de base va vous permettre
d'exercer un style de
programmation particulier dite
programmation procédurale,
ou impérative, qui a la particularité
de faire en sorte que les données et
les traitements apparaissent de façon
séparée dans un programme.
Certes, il y a une interaction entre les
deux.
Les traitements opèrent typiquement sur
les données, lesquelles influencent à leur
tour les traitements, mais les deux
entités apparaissent de façon séparée.
par exemple, comme nous allons le voir
dans quelques
minutes dans un exemple concret, les
traitements peuvent s'exprimer par
le biais de fonction, et le lien entre
données et
traitements peut se faire alors par le
biais du passage
des arguments, les données manipulées
apparaissent au
travers d'entités distinctes, comme par
exemple les variables.
Une des particularités fondamentales de
l'orienté objet est de permettre
le regroupement des traitements et des
données en une seule et même entités.
Partons d'un exemple concret.
Imaginons que je souhaite écrire un petit
programme C++ qui calcule
la surface d'un rectangle.
Le rectangle est représenté au moyen d'une
largeur et
d'une hauteur, et je souhaite donc en
calculer la surface.
- 2. En programmation procédurale, j'aurais
tendance à procéder de cette façon,
donc je vais commencer par déclarer une
variable, la largeur,
pour représenter la larguer du rectangle,
que j'initialiserai avec une
valeur appropriée, et je vais faire de
même pour la hauteur.
Ensuite, le calcule de surface peut se
faire en passant la largeur et la hauteur
ainsi définies à une fonction surface, qui
se charge de réaliser le calcul approprié.
Alors on voit bien au travers de cet
exemple que dans
un tel programme, les données et les
traitements apparaissent de façon séparée.
Les variables me permettent de stocker et
de modéliser les données dont
j'ai besoin, et les traitements sont
réalisés par le biais de fonction.
Il n'existe pas de lien direct entre les
données et
les traitements.
Le lien s'établit uniquement ici par le
biais du passage des arguments.
Alors une des critiques fondamentales que
l'on peut émettresur ce genre
de programmes est l'absence de lien
sémentique entre les différentes entités.
Par exemple, le lien sémentique qui unit
la largeur et la hauteur, il
s'agit de la largeur et de la hauteur d'un
rectangle, est difficile à établir.
Imaginons par exemple que je ne soit
pas francophone, et donc que pour moi
largeur
et hauteur soient des noms peu parlants,
il est
difficile pour moi de voir qu'il s'agit de
deux
entités liées à un même concept, celui de
rectangle.
Alors le lien sémentique qui unit ici les
données entre elles n'est pas explicite.
Mais il en va de même pour le lien qui
unit les données et les traitements qui
agissent sur ces données.
Imaginons que je sois un petit peu moins
précautionneuse dans le
choix de mes identificateurs, et que
j'appelle cette fonction produit, par
exemple.
De même, imaginez que j'appelle mes
arguments, que je leur donne
des noms moins parlants, du coup, il
devient très difficile, pour
quelqu'un qui lit le code, de voir que
l'on est effectivement
ici en train de faire un calcul de surface
sur un rectangle.
Or ce lien existe conceptuellement.
Ici c'est bel et bien la largeur et la
hauteur d'un rectangle
que je suis en train de manipuler, et ici
- 3. c'est bien la surface
d'un rectangle que je souhaite calculer.
Donc le fait de pouvoir regrouper en une
seule et même entité la notion
de rectangle les données caractéristiques
du rectangle,
comme sa largeur et sa hauteur, ainsi que
les traitements qui
lui sont spécifiquement associés, va me
permettre d'établir un lien explicite
entre ces différentes entités et de donner
beaucoup plus de clarté à mon programme.
Il s'agit là de l'un des fondamentaux de
la programmation orientée objet.
Ce qu'il faut savoir de façon globale,
c'est que la programmation orientée objet
va vous
donner un certain nombre d'outillage
permettant davantage de robustesse, de
modularité, de lisibilité, à vos
programmes.
Ce qui va dans le sens d'une meilleure
maintenabilité.
Alors robustesse par rapport aux
changements, si votre programme est
amené à changer un jour, à être étendu, on
ne
veut pas être dans l'obligation de tout
réécrire, et de
robustesse face aux erreurs de
manipulation, par exemple, des données.
En effet, la plupart des applications
développées
de nos jours ne sont pas redéveloppées
depuis zéro mais consistent à étendre, Ã
maintenir du code existant,
et il est important de pouvoir le faire Ã
moindre coût.
Nous allons voir que les propriétés
fondamentales de l'orienté objet, à savoir
davantage de robustesse, de modularité et
de lisibilité vont exactement dans ce
sens-là .
La programmation orientée objet offre en
réalité quatre concepts
centraux: encapsulation, abstraction,
héritage et
polymorphisme, qui permettent de mieux
organiser les programmes dans le sens de
la robustesse,
lisibilité, modularité, et maintenabilité,
comme je l'ai expliqué tout Ã
l'heure, et ces concepts centraux, ne sont
pas spécifiques Ã
un language il s'agit des concepts
centraux de l'orienté objet.
Dans la séquence présente nous allons
essentiellement
nous occuper de définir encapsulation et
abstraction,
et nous aborderons dans les séquences
suivantes
les notions fondamentales d'héritage et de
polymorphisme.
- 4. Commençons par la notion d'encapsulation.
Ce que l'on entend par encapsulation,
c'est
le fait de pouvoir regrouper dans une
seule et même entité des données et des
traitements qui agissent sur ces données.
Donc typiquement, dans l'exemple du
rectangle, nous
allons regrouper dans une seule et même
entité la largeur et la hauteur qui
caractérisent la représentation du
rectangle et la fonctionnalité
du calcul de surface.
Donc en terme de jargon, on va parler pour
les données de la notion d'attributs, et
on va parler pour les fonctionnalités de
la notion de méthodes.
Donc, en programmation orientée objet, on
va être dans la
possibilité de définir des nouveaux types
de données, au travers,
nous allons le voir un petit peu plus
tard, de
la notion de classe, ces types de données
peuvent être utilisés
pour travailler concrètement avec des
données, de
types plus abstraits, le rectangle, et ces
données vont
être des objets qui vont cohabiter dans le
programme et interagir dans le programme.
Un programme orienté objet va donc
typiquement travailler avec des
objets, qui sont caractérisés par leurs
attributs, et leurs méthodes.
Parlons maintenant
de la notion d'abstraction.
Supposons que je souhaite écrire un
programme
qui manipule plusieurs rectangles, pas
uniquement un seul.
Donc dans une approche procédurale,
j'aurai typiquement l'occasion de déclarer
autant de largeurs et de heuteurs que j'ai
de rectangles, ici je l'ai
fait pour le premier rectangle, et je
devrai bien évidemment faire la
même chose pour le second rectangle, ce
qui est relativement fastidieux.
Si je souhaite maintenant calculer la
surface de
ces deux rectangles, je vais devoir
appeler la
fonction surface, lui fournissant comme
argument la largeur
et la hauteur de chacun des rectangles de
façon
appropriée, donc je fais un appel à la
fonction
surface, en lui fournissant à chaque fois
les bons arguments.
Donc il faut être précautionneux, ceci est
source d'erreur.
Imaginez par exemple que je me trompe et
- 5. qu'au lieu de
passer au premier appel de surface non pas
hauteur un, mais
hauteur deux, je perds la cohérence de mes
données, je ne
suis plus en train de travailler avec des
données d'un même rectangle.
Le processus
d'abstraction est le processus au terme
duquel
je réalise que je manipule en réalité les
données d'un plus haut niveau, la notion
de
rectangle, Ã laquelle je peux associer une
notion
générique, ici tous les rectangles sont
caractérisés par
une largeur, et une hauteur, auxquelles on
peut
attacher un même calcul de surface, du
coup
je décide de travailler avec la notion
plus
abstraite de rectangle, plutôt que de
travailler
avec une description intime de la
représentation des
rectangles, ce qui nous permet de nous
focaliser sur l'essentiel et de cacher les
détails.
L'encapsulation, qui me permet de
regrouper en une
seule et même entité des données comme une
largeur
et une hauteur, et des traitements comme
un
calcul de surface, est l'outil qui me
permet d'aboutir
à une vision plus abstraite des objets que
je manipule.
Du coup je vais permettre à mon programme
de désormais pouvoir travailler avec des
entités plus abstraites.
je vais travailler avec un premier
rectangle et un deuxième rectangle, tous
les deux de type rectangle dans le
programme, je vais invoquer des
calculs de surface sur ces rectangles, on
va voir qu'en orienté objet,
j'anticipe un petit peu sur la suite, que,
on va dire, je calcule
la surface du premier rectangle, donc on
va voir par la suite comment
tout ceci s'exprime de façon beaucoup plus
précise, et du coup je permets Ã
mon programme de se focaliser sur
l'essentiel, Donc je ne suis plus en train
de me préoccuper du fait qu'un rectangle
est constitué d'une largeur et d'une
hauteur.
Je me focalise sur les aspects essentiels.
Je travaille avec des rectangles.
Je fais un calcul de surface
sur un rectangle.
- 6. Au niveau de l'utilisation, je ne vois
désormais du
rectangle que ce que l'on appelle en
jargon orienté objet
son interface d'utilisation c'est-Ã -dire
les fonctionnalités qui sont offertes pour
la manipulation du rectangle comme ici le
calcul de surface.
Faisons une analogie avec un objet de la
vie courante.
Lorsque vous conduisez votre voiture vous
n'avez
typiquement besoin d'en connaître que
l'interface d'utilisation, concrètement.
Eh bien, vous avez besoin d'avoir
un volant, un accélérateur, une pédale de
frein et à nul moment
il ne vous est utile de savoir comment le
moteur est concrètement construit.
Donc, pour conduire votre voiture, vous
n'avez besoin d'en connaître que
l'interface d'utilisation.
C'est exactement le même principe en
programmation orientée objet.
Pour utiliser un nouveau type d'objet, une
nouvelle classe, vous n'avez
besoin d'en connaître que l'interface
d'utilisation
qui est prévue par le programmeur
de la classe.
Le reste est du détail d'implémentation
interne qu'il n'est pas utile de
connaître.
Il y a donc deux niveaux de perception
d'un objet.
Un niveau externe qui est utile au
programmeur utilisateur,
celui qui utilise la notion de rectangle,
dans un programme.
Donc il existe désormais un type
rectangle.
Je peux déclarer des variables de
type rectangle, les initialiser de façon
appropriée.
Et ensuite,
ce qui m'intéresse en tant que programmeur
utilisateur,
c'est les fonctionnalités utiles: le
calcul de surface.
Donc ce niveau externe est le niveau qui
intéresse
le programmeur utilisateur, celui qui
utilise le type rectangle.
Second niveau.
Le niveau interne, celui qui a programmé
le nouveau type, le type rectangle,
a dû se préoccuper de tous les détails
d'implémentation.
A savoir que un rectangle c'est une
hauteur et une largeur,
il a dû définir comment se fait
concrètement le calcul de surface.
Donc ceci constitue la partie
implémentation.
- 7. C'est un niveau interne qui n'est pas
forcément utile à celui qui utilise le
rectangle.
Donc en programmation orientée objet, nous
aurons
non seulement la possibilité de regrouper
en une seule et même entité des
données et des traitements mais on va
pouvoir également définir des niveaux de
visibilité.
Celui qui programme le nouveau type
rectangle, concrètement une classe
rectangle, va pouvoir
dire ceci est un détail d'implémentation
qui
ne sera pas accessible à l'utilisateur
externe.
Ceci est une fonctionnalité que l'on
souhaite offrir à l'utilisateur externe et
donc
qui va être accessible à cet utilisateur.
Tout ce qui est accessible à l'utilisateur
donc visible constitue ce
que l'on appelle l'interface d'utilisation
de la classe du type en question.
Donc ici, dans notre classe rectangle,
dans notre
nouveau type rectangle, nous avons défini
comme interface d'utilisation
le calcul de surface.
Le reste constitue des détails
d'implémentation qui ne sont
pas accessibles aux programmeurs qui
utilisent le type rectangle.
Et nous avons ici une clé fondamentale
nous permettant d'atteindre
l'objectif d'une meilleure robustesse de
nos programmes face au changement.
Typiquement, lorsque vous changez de
voiture, même si la
technologie du moteur a changé,
l'interface, globalement, reste la même.
Vous pouvez continuer, vous savez toujours
conduire votre voiture même si les détails
internes de
mise en oeuvre de votre voiture ont changé
entre-temps.
Donc vous ne voyez de votre voiture que
quelque chose d'abstrait.
Vous ne voyez de votre voiture
concrètement que ce qui vous permet
de la conduire, Ã savoir le volant,
l'accélérateur, la pédale de frein.
Pour résumer, encapsuler c'est regrouper
en une seule et même entité
les données et le traitements qui la
caractérisent, c'est aussi dissimuler les
détails d'implémentation.
L'interface d'utilisation que l'on définit
en
utilisant justement cet outillage de
l'encapsulation.
Et ce qui me permet d'aboutir à une
abstraction donc une visions abstraite des
- 8. objets que en
tant qu'utilisateur externe, je ne vois
plus que en
tant qu'objets manipulables au travers de
leur interface d'utilisation.
Donc concrètement, le programmeur
concepteur
va donc décider de l'existence d'un
nouveau type et va devoir se préoccuper de
tous les détails d'implémentation.
Il va devoir
décider de ce qui est visible pour le
monde extérieur, de ce qui est
utilisable et de ce qui ne l'est pas.
Le programmeur utilisateur,
de son côté, est client du nouveau type de
données.
Il va pouvoir l'utiliser.
Mais va pouvoir
l'utiliser uniquement au travers de
l'interface, c'est-Ã -dire des
méthodes visibles typiquement et n'aura
pas accès aux détails
internes.
L'interface d'utilisation est typiquement
ce qui va permettre d'établir le
lien entre le concepteur développeur et
l'utilisateur.
Et de façon très concrète, cette interface
va pouvoir être entièrement décrite par
l'entête des méthodes qui sont offertes au
programmeur utilisateur.
Alors, un des intérêts fondamentaux de
l'orienté objet est
d'assurer une meilleure visibilité, une
meilleure cohérence au programme.
Vous avez ici sous les yeux deux
programmes qui font
sensiblement la même chose, qui calculent
la surface
d'un rectangle de largeur trois et de
hauteur quatre.
Nous anticiperons un tout petit peu sur la
suite des séquences à venir, Ã
savoir que l'on peut initialiser la
largeur
et la hauteur d'un rectangle selon cette
syntaxe-là .
Mais c'est surtout pour vous donner un
aperçu de ce
que donnerait au final le programme en
terme de lisibilité.
Donc si l'on compare les deux approches,
eh bien on se rend
compte qu'ici nous sommes en train de
travailler avec des données de très
bas niveau.
Ici nous travaillons avec un rectangle, ce
qui
est beaucoup plus informatif quant aux
intentions de programmation.
En lisant ce programme, on sait tout de
suite
que l'on est en train de travailler avec
- 9. un rectangle.
Ici le lien entre les données et les
traitements ne se fait
que par le biais du passage des arguments
ce qui est indirect.
Alors qu'ici le lien peut se faire de
façon explicite.
On appelle un calcul de surface sur un
rectangle donné.
De plus, de par le fait qu'en orienté
objet on ne peut manipuler l'objet
qu'au travers des méthodes des
fonctionnalités de
l'interface d'utilisation, cela permet de
donner un
cadre d'utilisation qui est beaucoup plus
rigoureux.
Par exemple ici, comme montré tout Ã
l'heure, rien ne m'oblige dans cette
approche à avoir des valeurs cohérentes
ici pour la largeur et la hauteur.
Donc la largeur et la hauteur qui
appartiendraient à un même rectangle.
Ici, cette erreur n'est plus possible
puisque
on manipule un rectangle particulier et
donc forcément
les valeurs qui y sont liées seront
cohérentes.
De plus, de par le même fait que
l'utilisateur est contraint de passer par
l'interface d'utilisation pour manipuler
l'objet, il n'est
pas impacté par des modifications
d'implémentation interne.
Donc comme évoqué précédemment si on
décide
plutôt, au lieu de modéliser un rectangle
comme étant un objet de T, de
deux attributs de type double, hauteur et
largeur.
On passe plutôt à une
représentation sous la forme d'un tableau
à deux entrées qui seraient les
dimensions.
Alors, si le programmeur concepteur de la
classe
rectangle a bien fait son travail et donc
a
adapté en conséquence le calcul de
surface, celui
qui utilise le calcul de surface n'est pas
impacté.
Voilà donc nous venons de voir sur ce
petit
exemple l'intérêt qui découle de séparer
le niveau interne
du niveau externe.
On obtient donc un cadre d'utilisation
plus rigoureux et
les modifications de la structure interne
restent invisibles à l'extérieur.
Donc une des règles fondamentales qu'on a
déjà vue dans l'introduction.
- 10. Les attributs, de par le fait qu'il faut
faire
des choix quant à leur modélisation, des
choix techniques,
des choix d'implémentation sont considérés
comme des détails d'implémentation
de façon systématique dans tout bon
programme orienté objet.
Pour résumer, lorsque l'on
définit un nouveau type d'objet au travers
d'une classe, on va être amené
à définir les attributs caractéristiques
de la classe ainsi que les méthodes qui
lui sont spécifiques et on va devoir se
préoccuper de définir concrètement ce
qui est visible, l'interface
d'utilisation, et ce
qui ne l'est pas, les détails
d'implémentation.
Donc une fois que on aura décidé de ce qui
est à cacher, on va aboutir à une
représentation de
l'objet pour l'utilisateur externe qui se
résume à l'interface d'utilisation.
Donc l'utilisateur externe n'aura de
vision de cet objet
qu'une vision abstraite qui
est matérialisée par l'interface
d'utilisation.
Je ne vois d'un rectangle que son calcul
de surface.
Et si je veux suivre les bonnes règles de
programmation orientée
objet, je vais considérer que les
attributs sont également des détails
d'implémentation
et donc l'interface va se limiter à un
certain nombre de méthodes
bien choisies.
L'utilisateur externe n'aura de vision de
l'objet que cette interface d'utilisation.
À retenir de la séquence d'aujourd'hui le
résultat du processus d'abstraction et ce
que l'on appelle une classe
qui me permet de
désigner une catégorie d'objets.
Une classe va définir dans un programme un
nouveau type.
Je dispose désormais du type rectangle que
je peux manipuler dans un programme.
Je peux déclarer des variables de ce
type-là .
Une réalisation concrète donc la
déclaration d'une variable
de ce nouveau type est ce qu'on appelle
dans le jargon orienté objet, un objet.
Et elle est généralement manipulée au
travers d'une variable.
À noter que l'on utilise couramment le
terme d'instance pour désigner un objet.
Pour résumer et pour illustrer le propos,
celui qui
écrit la classe rectangle va décider de
l'existence conceptuelle de
- 11. ce nouveau type puis va le faire
évidemment en écrivant
un programme qui contient le code de cette
classe rectangle.
Celui qui
l'utilise va utiliser ce nouveau type en
déclarant des variables du type
rectangle qui vont avoir une existence
concrète au moment où le programme
s'exécute
donc au moment où on crée de nouveaux
objets de ce type-là ,
on va pouvoir commencer à travailler avec
ces objets concrètement dans un programme.
Donc l'utilisateur va travailler
concrètement avec des
réalisations des objets de la classe
rectangle.
Nous voici arrivés au terme de la
présentation de ces concepts
centraux de l'orienté objet que sont
l'encapsulation et l'abstraction.
Vous allez, dès les séquences suivantes,
commencer
à les pratiquer très concrètement en C++.