« Langage C++/Objet » : différence entre les versions

Une page de Wikiversité, la communauté pédagogique libre.
Contenu supprimé Contenu ajouté
Ppignol (discussion | contributions)
Ppignol (discussion | contributions)
Ligne 269 : Ligne 269 :
// Destructeur
// Destructeur
virtual ~Carre();
virtual ~Carre();
virtual void mDessine();
};
};


Ligne 286 : Ligne 287 :
Carre::~Carre()
Carre::~Carre()
{
{
}

void Carre::mDessine()
{
//Dessine ici le Carre (vrais code de dessin non pertinent car trop volumineux pour le gain obtenu sur l'intérêt de la leçon).
cout << "Dessine Carre :\n" << endl;
Rectangle::mDessine();
}
}


Ligne 302 : Ligne 310 :
Rectangle(double pArreteAB, double pArreteBC);
Rectangle(double pArreteAB, double pArreteBC);
virtual ~Rectangle();
virtual ~Rectangle();
virtual void mDessine();
};
};


Ligne 315 : Ligne 324 :


// Destructeur
// Destructeur
virtual Rectangle::~Rectangle()
Rectangle::~Rectangle()
{
{
}

void Rectangle::mDessine()
{
//Dessine ici le Rectangle (vrais code de dessin non pertinent car trop volumineux pour le gain obtenu sur l'intérêt de la leçon).
cout << "Dessine Rectangle :\n" << endl;
Parallelogramme::mDessine();
}
}


Ligne 372 : Ligne 388 :
virtual void mAngleA(double& pAngle)
virtual void mAngleA(double& pAngle)
{
{
Quadrilatere::mAngleA(pAngle);
}


virtual void mAngleB(double& pAngle)
{
Quadrilatere::mAngleB(pAngle);
}

virtual void mAngleC(double& pAngle)
{
Quadrilatere::mAngleC(pAngle);
}

virtual void mAngleD(double& pAngle)
{
Quadrilatere::mAngleD(pAngle);
}
}


virtual void mAngleB(double& pAngle);
virtual void mAngleC(double& pAngle);
virtual void mAngleD(double& pAngle);
// Constructeur parametré
// Constructeur parametré
Parallelogramme::Parallelogramme(double pArreteAB, double pArreteBC, double pAngleA):Quadrilatère(pArreteAB, pArreteBC, pArreteAB, pArreteBC, 0.0, 0.0, 0.0, 0.0)
Parallelogramme::Parallelogramme(double pArreteAB, double pArreteBC, double pAngleA):Quadrilatère(pArreteAB, pArreteBC, pArreteAB, pArreteBC, 0.0, 0.0, 0.0, 0.0)
Ligne 402 : Ligne 430 :
virtual void Parallelogramme::mDessine()
virtual void Parallelogramme::mDessine()
{
{
//Dessine ici le Losange (vrais code de dessin non pertinent car trop volumineux pour le gain obtenu sur l'intérêt de la leçon).
//Dessine ici le Parallelogramme (vrais code de dessin non pertinent car trop volumineux pour le gain obtenu sur l'intérêt de la leçon).
cout << "Dessine Parallelogramme :\n" << endl;
cout << "Dessine Parallelogramme :\n" << endl;
Quadrilatere::mDessine();
Quadrilatere::mDessine();

Version du 3 mai 2009 à 23:49

Début de la boite de navigation du chapitre
Objet
Icône de la faculté
Chapitre no {{{numéro}}}
Leçon : Langage C++
Chap. préc. :Structures, unions et champs de bits
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Langage C++ : Objet
Langage C++/Objet
 », n'a pu être restituée correctement ci-dessus.


Programmation orienté objet

Historique

Préhistoire : la programmation monolithique séquentielle

Au temps des prémices de l'informatique, à l'époque des dinosaures électromécaniques qui faisait le volume d'un bâtiment de quatre étages, où les bandes magnétiques n'existaient pas encore, où la programmation se faisait en connectant des câbles et des trous ou pour les plus sophistiqués par cartes perforés, où le moindre calcul scientifique prenait plus de six mois à la main (et à la règle à calcul) et seulement une semaine une fois informatisé, la programmation était monolithique. C'est à dire que les développeurs de l'époque faisaient des programmes (la plus part du temps en binaire) de manière séquentielle et s'adressaient directement au systèmes physique de traitement qui sera nommé plus tard et après miniaturisation : processeur. De fait le programme en question était le seul programme que la machine connaissait. Il n'y avais donc pas de traitements parallèles ni de problèmes d'accès concurrentiel aux ressources. Le programme était purement séquentiel et on recréais un nouveau programme pour chaque nouvelle application (qui à l'époque n'étaient que des calculs). Les programmes étaient courts (de quelques centaines à quelques milliers d'ordres machines simples). Le terme de "BUG" (insecte en anglais) est introduit à cause des cafard qui venaient se réchauffer un peu trop près de l'ancêtre des transistors, des lampes à tubes électrostatiques, et finissaient par griller entre les pâtes du composent ce qui entrainaient des panes longues et coûteuse à réparer.

Antiquité : la programmation procédurale

A cette époque le transistor en silicium à déjà été intégré aux ordinateurs qui n'occupent plus qu'une salle de réunion entière. Les bandes magnétiques sont devenues monnaies courantes et les programmeurs développent maintenant sur un clavier et un écran. Les premiers compilateurs pour langages extensible lui permettent grâce au concept de procédures et de fonctions d'enrichir le langage de base dont il dispose en composant des suites d'instructions qu'il peut réutiliser. On développe alors ses programmes de manière fonctionnelle affin de pouvoir décomposer un traitement en sous-traitements de plus en plus simples.


Moyen-Age : La programmation orienté donnée

A ce moment le processeur viens de voir le jours. Les ordinateurs occupent le volume d'une armoire. Les premiers systèmes d'exploitations mono-taches voient le jours. Le programmeur définit toutes les variables dont il aura besoins et les regroupent par fonctionnalités dans des fichiers distincts avec les procédures et fonctions qui les traitent.


Renaissance : La multiprogrammation

A ce stade les processeurs/micro-contrôleurs sont courent. Les ordinateurs ont la taille de valises. Les premiers systèmes d'exploitation en ligne de commandes permettent une utilisation facilitée de l'ordinateur qui intègre désormais un disque dur et un lecteur de disquettes. Les programmeurs de l'époque commencent à utiliser le PIC (Programmable Interrupt Controller) au maximum de ses ressources. Les programmes résidents voient le jours. Le programme ne travaille plus seul sur la machine, il doit cohabiter avec les autres programmes résidents dans la machine. Les concepts de processus et de threads voient le jours et les problèmes de concurrences d'accès aux ressources aussi. Les premiers réseaux d'entreprises voient le jours. Les programmes


Temps Modernes : La programmation orienté objet

A l'époque les industries du logiciel se heurtent de plus en plus à la difficulté de développer des projet de taille toujours croissante. Pire plus il y à de monde sur le projet moins le projet devient gérable avec les outils de l'époque et ce même pour des projets simples. La programmation orienté objet est un concept récupéré de l'ingénierie du bâtiment qui permet de décrire les différents aspects du logiciel de manière formelle et ainsi maitriser les coûts, les délais, les risques et assurer la qualité du logiciel développé.


Pourquoi la programmation orienté objet

En 1995 le Standish Group édita un rapport édifiant intitulé "CHAOS" sur l'état des échecs de projets informatiques dans plus de 360 sociétés américaines. Ce rapport fait référence et fait état que les entreprises américaines et organismes gouvernementaux perdent des centaines de milliards de dollars par ans, de ne pas appliquer les mêmes règles de conception et de gestion du risque que dans les domaines de génie civil. En effet ce rapport dénonce une renonciation des cadres techniques à appliquer les même méthodes que leurs homologues du bâtiment, pour la plupart du temps des raisons politiques de rond de jambes et, pour les autre rares cas, d'incompétences de capture des besoins ou de diagnostic techniques.

  • 16% de projets conformes aux prévisions initiales en temps et couts (mais souvent diminués en fonctionnalités),
  • 53% de projets dépassements en coûts et délais d'un facteur 2 à 3,
  • 31% de projets abandonnés.

Depuis le Standish Group édite des mises à jours annuelle de ces chiffres et malheureusement les chiffres restent très alarmants et surtout très réels.

En effet le logiciel étant un produit qui n'a de consistance qu'au travers de l'ordinateur qui l'exécute les ingénieurs de l'époque n'arrivent pas à concevoir qu'il est aussi difficile d'abattre un mur porteur dans un immeuble que de redévelopper un ensemble de méthodes imbriqué les unes aux autres dans du code métier.


Qu'est-ce que la programmation orienté objet

Le paradigme de la programmation orienté objet repose entièrement sur la classe à qui l'on délègue la gestion des données que l'on encapsule en elle en y accédant via son interface représenté par ses méthodes appelés aussi accesseur/mutateur/propriétés. Les concept d'abstraction, de généralisation, d'héritage, et de polymorphisme sont aussi cruciaux.


De nos jours : La programmation événementielle

Grâce aux avancées technologiques en graphisme l'entreprise Xerox développe une interface graphique pour ses imprimantes qui sera ensuite repris par Steeve JOBS pour le compte du nouvel ordinateur d'Apple le Maccintosh. Bill GATES, voyant une menace en ce concurrent pour son MS DOS, il débauche des ingénieurs d'Apple et crée son Windows 3.1x. La technologie basé sur les Interruptions du PIC permet de recevoir et propager et traiter en temps réels les événements utilisateur. Les jeux informatiques voient le jours, les interfaces graphiques facilitent grandement l'accès à l'ordinateur qui se démocratise. Un enfant est capable d'utiliser un ordinateur. Internet voit le jour et se répand. Les "Design patterns" du GoF et du GRASP améliorent la réutilisabilité des développements en exposant des problèmes récurent de la programmation et leur solution-types.


Demain : La programmation orienté agent

La programmation orienté agent est une évolution de la programmation orienté objet. Au lieux d'utiliser des objets classiques uniquement réactifs on utilise des objets composites comme les automates et/ou les réseaux neuronaux affin de rendre les objet du moins en partie pro-actif en fonction de leurs états interne, de l'état de leur environnement et des taches qu'ils ont à réaliser. Le but du jeu est de rendre les agent autonome et indépendant de l'interface de leurs voisins. Ainsi cela permet une plus grande souplesse de dialogue entre les agents. Cela pose toutes fois l'éternel problème de la construction des moteur de tels agents. En effet, au plus un comportement logiciel est sophistiqué, au plus le développement sera difficile, coûteux et instable. De nombreux frameworks voient le jours et comme tous les framework standards actuels ils sont incompatibles entre eux, un comble pour une architecture qui se veux massivement compatibilisante. Peut-être qu'un jour les interfaces s'uniformiseront mais en attendant vu la complexité de la mise en œuvre de ces agents il me semble (et cela n'engage que moi) prématuré de les utiliser dans des solutions industrielles de grande envergure hormis éventuellement pour le domaine de la robotique et des domaines associés qui sont encore des domaines de recherche qui ne sont pas encore entrés dans le domaine du grand public.


L'Objet

Dans le domaine de l'informatique, un objet est quelque-chose de concret au sens mathématique du terme que l'on peut manipuler ou dont on peut récupérer des informations.

  • Un cercle de rayon 0.8 cm,
  • Un triangle équilatéral de 3 cm de coté ou encore
  • Un cube de 5 cm d'arêtes.

Les moules ou modèles qui ont permis d'obtenir ces figures (respectivement

  • (pi*(R*R)),
  • (a*a)=(b*b)+(c*c)-(2*B*C*Cos(A)) où A=B=C=60°, et
  • a*a*a)

correspondent en C++ à ce que l'on appelle des Classes.

Pour faire une analogie avec le moulage, on pourrait dire que l'objet correspond à la figurine de plâtre peinte et vernie, et que la classe correspond au moule en caoutchouc qui lui à donné forme.

En développement on parle plus souvent d'instance de classe pour désigner un objet.


La Classe

En programmation orienté objet, tout est basé sur le concept de la classe. La classe est une entité autonome capable de maintenir une fois instanciée la cohérence des données qu'elle est chargée d'entretenir. (nous mettrons en évidence la syntaxe dans la partie implémentation)


Les Visibilités : Niveaux de visibilité dans une classe

Il existe trois niveaux de visibilité dans une classe :

  • Privé (Niveau par défaut) : Permet de masquer complètement les données et méthodes à des classes tiers même aux classes dérivées.On parle d'encapsulation. (Peut être utilisé par les méthodes mais est surtout destiné aux variables d'une classe)
  • Protégé : Permet de partager des données uniquement aux classes dérivés. (Normalement uniquement utilisé pour les méthodes)
  • Public : Permet de partager les données avec toutes les classes tiers. (Normalement uniquement utilisé pour les méthodes)


Les Attributs : Variable de classes

La classe est une entité qui peut être composé de variables internes que l'on nome attributs membre.

La théorie veut que les attributs d'une classe ne soient accessibles directement qu'à cette classe. Bien qu'en C++ il est possible de définir des attributs membres de visibilité publique ou protégés, il est normalement indispensable de rendre les attributs membres privés (je n'ai jamais eu à rendre publique ou même protégé des attributs hormis pour des classes-énumérations que nous verrons plus tard).

En effet le paradigme objet étant de rendre la classe responsable de la gestion de ses attributs pour assurer la gestion correcte et la cohésion des données, il serais mal vu qu'une autre classe accède malencontreusement à ces attributs et vienne bouleverser l'organisation que la classe permet de maintenir entre eux. Quand une classe privatise un attribut on dit qu'elle l'encapsule.


Les Méthodes : Méthodes de classes

Nous avons déjà vu les méthodes auparavant.

Les méthodes déterminent le comportement qu'une classe est capable de réaliser. La classe possède au moins trois méthodes spéciales :

  • Un constructeur sans paramètres appelé aussi constructeur par défaut ou, selon le cas, un constructeur paramétré qui oblige à fournir des paramètres pour créer la classe.
  • Un constructeur par copie qui prend en paramètre une référence sur la même classe (dans la majorité des cas elle est fournie par défaut par le compilateur mais il peut être fréquemment nécessaire de la définir manuellement).
  • Un destructeur qui est chargé d'appliquer un traitement pour éventuellement nettoyer la mémoire. Bien qu'il soit souvent vide l'implémenter explicitement et systématiquement permet de faire fonctionner le mécanisme d'héritage polymorphe.

Les méthodes donnant accès aux attributs sont appelés suivant le sens d'accès :

  • Des accesseurs pour les méthodes accédant en lecture seule.
  • Des mutateurs pour les méthodes accédant en écriture seule.
  • Des propriétés pour les méthodes accédant en écriture et en lecture.

La théorie veux que tout attribut qui aurais besoins d'être transmis ou modifié, le soit par l'une de ces méthodes d'accès. (Je n'ai jamais eu à faire autrement.) Les méthodes définissent aussi l'interface d'une classe.


Généralisation de classes

En programmation orienté objet, on peut structurer une hiérarchie de classes en arborescence. Les classes du sommet de l'arbre sont les classes les plus abstraites et générales. Les classes les plus profondes sont les plus concrètes et les plus spécialisés.

Pour illustrer ces propos supposons les figures suivantes :

  • Carre
  • Rectangle
  • Parallélogramme
  • Losange
  • Quadrilatere
  • Cercle
  • Ovale

Imaginons maintenant que nous devions réaliser des classes basé sur ces figures. Supposons que chaque classe doit être capable de de dessiner. Supposons aussi que nous devions gérer chacune de ces classes avec le même (et unique) pointeur et appeler la même méthode pour le déssin de toutes les classes.

Dans les figure imposés on peut voir que "Carre" est un cas particulier de (ou "une sorte de") "Rectangle" qui est lui même un cas particulier du "Parallélogramme" qui est lui même un "Quadrilatere". Quant à "Losange" c'est un "Quadrilatere"

On peut voir aussi que "Cercle" est un cas particulier de "Ovale".

Cependant rien ne lie Ovale et Quadrilatere.

Nous allons donc devoir généraliser Ovale et Quadrilatère en "Figure"

Ainsi Quadrilatère et Ovale sont des Figures ainsi un pointeur sur figure est capable de manipuler n'importe quelle classe sous-jacente Puisqu'après tout un carré est une figure tout comme un cercle.

Traitons maintenant le problème de la méthode unique. En fait depuis que l'on a créé la classe Figure ce n'est plus un problème.

En effet il suffit de définir la méthode "Dessine()" dans la classe Figure pour que toutes les autres classes en soient dotés.


Abstraction de classes

Lors de généralisations successives il est courent de se rendre compte qu'une classe est trop "abstraite" pour avoir suffisamment de données exploitable pour pouvoir en générer une instance. C'est le cas pour notre classe "Figure". En effet elle est trop générique et rien d'intéressant ne peut en sortir cependant elle fournis une interface que toutes les autres classes devront reproduire fidèlement. La méthode "Déssiner" ne représente pas grand choses pour une Figure. Nous ne pouvons pas décrire de comportement pour cette méthode dans cette classe. Nous ne pouvons donc pas instancier la classe car cela n'aurais pas de sens. Nous allons donc devoir rendre cette classe abstraite. Cela signifie que la classe n'est pas instanciable en l'état mais qu'une classe hérité non abstraite peut être interprété comme cette classe (via l'utilisations de pointeurs ou de références.)


Héritage de classes

L'héritage est la faculté qu'à une classe de pouvoir transmettre ses attributs et méthodes à ses classes dérivés et sous certaines conditions permettre à ses classes dérivées de redéfinir ses méthodes pour pouvoir les améliorer et les spécialiser.

Tout d'abord il faut savoir qu'en C++ une classe dérivé reçoit toujours une copie de l'intégralité des attributs et des méthodes de sa classe ancêtre. En C++, bien que l'on puisse choisir la façon dont sont copiés ces membres de la classe ancêtre la théorie objet veux que l'on hérite toujours des classes ancêtre de manière publique affin que tous les membres public de la classe ancêtre soient aussi disponibles de manière publique dans la classe dérivé. L'héritage permet de ne pas réécrire eternellement les mêmes codes, de réutiliser les objets, de pouvoir en spécialisermettre en œuvre le polymorphisme.


Polymorphisme de classes

Le polymorphisme en informatique se traduit par la capacité qu'a un pointeur de classe ancêtre présentant une interface donnée, à appeler la méthode de l'instance de la classe dérivé correspondant à la méthode de la classe ancêtre. En C++ la mise en œuvre du polymorphisme se fait à l'aide du mot clé "virtual". Dans la pratique et pour reprendre l'exemple vu précédemment :

Si l'on créé un pointeur sur Figure que l'on lui assigne l'adresse de l'instance d'un carré et que l'on demande au pointeur figure de se dessiner alors le pointeur vas appeler la méthode virtuelle et par le biais de l'héritage virtuel appeler la méthode implémenté dans la classe Carré.

Implémentation

Les classes sont donc la représentation physique du concept d'objet. Voici en C++ comment implémenter ces classe conformément à la théorie de l'objet.


Où <NomNouvelleClasse> est le nom de la classe, <ClasseAncêtre> est une classe ancêtre tout comme <AutreClasseAncêtre> et sont facultatives si la classe n'a pas à avoir d'ancêtre, <TypeAttribut1> et <TypeAttributN> sont les types des attributs, <NomAttribut1> et <NomAttributN> sont les noms des attributs, les attributs sont facultatifs, <TypeParamettre> et <NomParamettre> sont les paramètres des methodes de la classe, <TypeMethode1> et <TypeMethodeN> sont les types de retours des méthodes de la classe, <NomMethode1> et <NomMethodeN> sont les noms des méthodes de la classe, , <Visibilitée> peut prendre trois valeurs: public, protected ou private (par défaut). Un membre déclaré public peut être manipulé par n'importe quelle classe, un membre protected ne peut être manipulé que par la classe et ses dérivés tandis qu'un membre private ne peut être manipulé que par les méthodes de la classe.

Les méthodes sont soit définies directement dans la déclaration de classe (au quel cas ce sont des Macro), soit définies en dehors de la déclaration dans un fichier source séparé de la manière suivante:


Voici un exemple de la classe la plus simple à réaliser. Il faut dire aussi qu'elle ne fait strictement rien.

Début de l'exemple
Fin de l'exemple


Exemples:

Maintenant reprenons nos exemples de figures de tout à l'heure.

Début de l'exemple
Fin de l'exemple