Une classe est une entité complète, autonome, et le programmeur doit connaître à l’avance les membres qui la composent.
La seconde préoccupation est la visibilité de ces membres ; oublions les formules toutes faites "tous les champs sont privés et toutes les méthodes sont publiques". La réalité est toujours plus nuancée, et les méthodes d’accès pour ne pas dire les propriétés viennent rapidement contredire cette logique un peu simpliste.
La troisième recommandation est l’ordre dans lequel sont définis les membres. On peut commencer par les champs, puis les constructeurs, puis les méthodes publiques, enfin les méthodes non publiques. Il ne faut pas se priver de répéter pour chaque membre sa visibilité, ce qui évite des erreurs en cas de copiercoller intempestif. Pour chaque méthode, on doit bien avoir à l’esprit si elle est statique, virtuelle, abstraite… Pour chaque champ, il faut déterminer son type, son nom, sa visibilité s’il est statique, constant…
La dernière recommandation est la documentation. Une bonne classe doit être accompagnée de beaucoup de commentaires. Ceuxci ne doivent pas reformuler en moins bien ce qui est écrit en C++, mais plutôt développer des détails supplémentaires pour guider celui qui parcourt le code.
Comme C++ sépare la définition de l’implémentation, cela constitue une "optimisation" pour le programmeur. Le compilateur ne "moulinera" que les fichiers .cpp qui sont amenés à évoluer plus fréquemment que les fichiers .h, normalement plus stables car issus de l’étape de conception.
Le langage C++ propose plusieurs modes d’héritage. Le plus utilisé, à juste titre, est l’héritage public. À quoi peuvent bien servir les autres modes, comme l’héritage privé ?
L’héritage public spécialise une classe. La classe de base exprime le concept le plus général, le plus abstrait, alors que les sousclasses vont plus dans le détail. Mais il existe toujours en principe une relation "estuncas particulierde" entre la classe dérivée et la classe de base ; le livret est un cas particulier de compte. La voiture est un cas particulier de véhicule…
De ce fait il faut se méfier des constructions syntaxiquement possibles mais sémantiquement insensées. La classe ChameauSatellite n’a sans doute pas grande utilité. Une applet ne peut être aussi une horloge est un gestionnaire de souris. Ces constructions amènent de la complexité et produisent des programmes difficiles à maintenir. Fautil en conclure que l’héritage multiple est à proscrire ? Sans doute pas. C’est un mécanisme indispensable mais qui doit être envisagé avec des précautions. C++ étant un langage général, il permet l’héritage multiple, il en a même eu besoin pour la STL. Mais le programmeur doit être prudent s’il est amené à recourir luimême à cette construction. L’autre possibilité est de s’appuyer sur un framework qui prévoit l’héritage multiple dans un contexte encadré, sans risque.
Et l’héritage privé, finalement ? Là encore il s’agit d’un "truc" pour éviter l’explosion de code au sein d’un programme complexe. Tous les membres devenant privés, ce n’est pas pour créer des méthodes virtuelles que l’on recourt à ce mode. C’est pour repiquer du code sans trop s’embarrasser d’un quelconque polymorphisme. De nos jours, il existe d’autres façons de contrôler la quantité de code, et la programmation par aspects AOP (AspectOriented Programming) ouvre une voie prometteuse.
Il existe des outils d’analyse de logiciels C++ (profilers) traquant les goulets de performances, les fuites mémoire et les opérations litigieuses. Sous Unix, l’utilitaire purify sert précisément à détecter les erreurs d’implémentations d’un programme C++. Comme C++ est un langage assez proche de la machine, on peut également employer sous Windows l’utilitaire Process Viewer pour analyser le fonctionnement d’un programme.
4. Y voir plus clair parmi les possibilités de l’héritage
5. Analyser l’exécution d’un programme C++
enidentnumber-AAEAAAD/////AQAAAAAAAAAMAgAAAE1FTkkuRWRpdGlvbnMuTUVESUFwbHVzLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAJ0VOSS5FZGl0aW9ucy5NRURJQXBsdXMuQ29tbW9uLldhdGVybWFyawIAAAAHcGlzVGV4dAlwaWR0ZURhdGUBAA0CAAAABgMAAAA8NTI4MTQ0IC0gQWxnZXJpYSBFZHVjIC0gYWQwNWE2M2EtZGQ0Yi00ZGNkLWEzM2UtZGNjZWIzY2NiOTVlxA7dnxFVzogLAA==-enidentnumberLa conception d’un programme orienté objet en C++ est une étape qui demande une certaine maturation. L’essence d’un tel programme ne tient pas seulement à l’implémentation des algorithmes qu’il contient, mais bien à la structure des classes et aux relations qui les réunissent. Nous nous proposons dans cette partie de décrire les approches de méthodes liées à la conception de programme C++.
Avant tout, rappelonsnous que C++ a été créé pour créer des applications à destination du domaine des télécommunications. Ce langage n’est donc pas seulement un travail de recherche, mais c’est aussi le fruit d’un travail d’ingénierie informatique. Bien entendu, son concepteur Bjarne Stroustrup s’est assuré qu’il était suffisamment général pour être adapté à d’autres situations. Pour atteindre cet objectif, il a conçu trois éléments essentiels :
La méthode proposée par Bjarne Stroustrup repose évidemment sur son expérience de conception d’applications antérieure à C++. Elle repose sur un certain nombre de thèmes forts, dont certains sont des classiques des méthodes de conception logicielle, orientée objet ou pas. Citons parmi ces thèmes la délimitation du système, la complexité des applications par rapport au degré d’abstraction des outils, ou encore les cycles de conception et de programmation perçus comme des activités itératives. Le processus de développement minimal est constitué de trois étapes : L’étape qui nous préoccupe est justement celle de la conception. L’auteur de la méthode organise des sous processus à partir du découpage suivant : En conclusion, Bjarne Stroustrup a proposé une méthode générale, adaptée à la conception de programmes C++. Il est vraisemblable que le langage luimême a été influencé par la méthode de conception orientée objet. Un des intérêts de l’approche décrite cidessus vient du fait qu’elle est compatible avec un système de notation comme UML. De fait, UML a été créé à une époque où C++ était l’un des rares langages de programmation
La conception orientée objet
1. Relation entre la POO et la COO (Conception Orientée Objet)
a. L’approche initiale de C++ ● 1. le langage C++ en luimême. ● 2. la bibliothèque standard S.T.L. ● 3. une méthode de conception orientée objet pour C++. ● 4. analyse ● 5. conception ● 6. implémentation ● identification des classes, des concepts et de leurs relations les plus fondamentales ; ● spécification des opérations, c’estàdire des méthodes ; ● spécification des dépendances (des cardinalités diraiton en UML) ; ● identification des interfaces pour les classes les plus importantes ; ● réorganisation de la hiérarchie des classes ; ● utilisation de modèles pour affiner le diagramme. b. UML et C++ enidentnumber-AAEAAAD/////AQAAAAAAAAAMAgAAAE1FTkkuRWRpdGlvbnMuTUVESUFwbHVzLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAJ0VOSS5FZGl0aW9ucy5NRURJQXBsdXMuQ29tbW9uLldhdGVybWFyawIAAAAHcGlzVGV4dAlwaWR0ZURhdGUBAA0CAAAABgMAAAA8NTI4MTQ0IC0gQWxnZXJpYSBFZHVjIC0gYWQwNWE2M2EtZGQ0Yi00ZGNkLWEzM2UtZGNjZWIzY2NiOTVlyKe3qRFVzogLAA==-enidentnumberorientée objet disponibles pour l’industrie.
UML est l’acronyme d’Unified Modeling Language. Il s’agit d’un formalisme qui unifie les travaux en matière de conception orientée objet développés pour la plupart au cours des années 70 et 80. Ce formalisme est à l’initiative d’une société, Rational, qui a ensuite proposé le logiciel de modélisation Rose, ainsi qu’une méthode à part entière appelée RUP (Rational Unified Process).
Comme C++ était l’un des principaux langages du marché, il a fortement influencé l’élaboration d’UML. Aussi, estil fréquent de trouver des logiciels de modélisation UML capables de transformer automatiquement un diagramme de classes en programme C++, sans toutefois pouvoir fournir l’implémentation correspondante ! Le formalisme UML est constitué de neuf diagrammes. Il n’est souvent pas nécessaire d’exécuter l’ensemble des diagrammes pour parvenir à une modélisation conforme. Par contre, certains diagrammes nécessitent de s’y prendre en plusieurs fois. On parle alors d’un processus de modélisation itératif. Voici la liste des diagrammes constitutifs du formalisme UML 1 : Des outils tels que Visio Architect ou Enterprise Architect sont capables de reproduire le diagramme de classes d’un programme, ou inversement de générer du C++ à partir d’un diagramme de classes. Comme UML et C++ sont deux langages très généraux, ils s’entendent parfaitement et toute la panoplie du C++ peut être écrite en UML.
Le diagramme de classe suivant montre comment modéliser une partie du tableur
CLIMulticube
étudié auchapitre Les univers de C++. C’est bien la preuve que le formalisme UML n’est aucunement réservé aux langages C# ou Java. Diagramme des cas d’utilisations Vision fonctionnelle Pose les limites du système en listant les acteurs et leurs intentions, sans s’attarder sur le "comment". Diagramme de collaboration Vision fonctionnelle
Les cas d’utilisations sont décrits sous la forme de scénarii textuels avant d’être formalités en diagrammes de collaboration. C’est le début du comment.
Diagramme d’activité Vision
fonctionnelle
C’est une version "workflow" du diagramme de collaboration plus adaptée à la description de certains scénarii.
Diagramme état transition
Vision dynamique
C’est une version technique d’un workflow ; on considère des changements d’état au sein de classes qui prennent progressivement forme.
Diagramme de séquence Vision
dynamique
Semblable au diagramme d’état transition, le diagramme de séquence étudie la constitution de classe sous l’angle du temps, en faisant progressivement ressortir des méthodes.
Diagramme du domaine Vision "métier" Ce diagramme ne fait pas partie d’UML mais il est indispensable. Les classes métiers sont souvent structurées à partir des tables SQL.
Diagramme de classes Vision statique Ce diagramme est un peu la version affinée,
superposée des précédents diagrammes. Diagramme de
composants
Vision système Lorsque les classes sont stéréotypées, pourvues
d’interfaces techniques, elles sont appelées
composants. Diagramme de
déploiement
Vision système Ce diagramme précise sur quels nœuds les
composants doivent être déployés. enidentnumber-AAEAAAD/////AQAAAAAAAAAMAgAAAE1FTkkuRWRpdGlvbnMuTUVESUFwbHVzLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAJ0VOSS5FZGl0aW9ucy5NRURJQXBsdXMuQ29tbW9uLldhdGVybWFyawIAAAAHcGlzVGV4dAlwaWR0ZURhdGUBAA0CAAAABgMAAAA8NTI4MTQ0IC0gQWxnZXJpYSBFZHVjIC0gYWQwNWE2M2EtZGQ0Yi00ZGNkLWEzM2UtZGNjZWIzY2NiOTVlyKe3qRFVzogLAA==-enidentnumber
Les design patterns sont des façons standard d’aborder des problèmes logiciels. Il s’agit de constructions de classes répertoriées que l’on adapte (développe) au gré des besoins.
Nous avons vu au chapitre Les univers de C++ que les MFC implémentaient le pattern MVC (Modèle Vue Contrôleur) à travers l’architecture document vue. Il existe de très nombreux modèles et certains générateurs de code peuvent constituer l’ossature d’une application en paramétrant des patterns pour un langage donné. Pour illustrer les patterns, nous développons l’exemple du Singleton. Il s’agit de veiller à ce qu’une classe ne puisse être instanciée qu’une et une seule fois. Un exemple habituel d’utilisation de ce pattern concerne les fichiers de configuration qui ne doivent pas exister en plusieurs exemplaires au sein d’une application.
class Configuration {
private:
static Configuration* instance; Configuration()
{
// Le constructeur privé empêche toute instanciation externe à la classe
}
public:
static Configuration* getInstance() {
if(instance==NULL) {
printf("Instanciation de Configuration\n"); instance = new Configuration();
} return instance; } public: bool param1,param2; } ; Configuration* Configuration::instance=NULL;
int _tmain(int argc, _TCHAR* argv[]) {
// utilisation du singleton
Configuration::getInstance()->param1 = true;
2. Les design patterns
Configuration::getInstance()->param2 = true;
return 0; }
Comme le constructeur est privé, il est impossible d’instancier la classe
Configuration
en dehors de celleci.Cependant, nous pouvons confier cette instanciation à une méthode publique et statique,
getInstance()
.Cette dernière s’appuie sur un champ statique,
instance
, pour contrôler l’unique instanciation de la classe. Aupremier appel de