ÉCOLE POLYTECHNIQUE DE MONTRÉAL DÉPARTEMENT DE GÉNIE INFORMATIQUE LOG2410 Conception logicielle Solutionnaire examen final Automne 2008 Documentation : Aucune Calculatrice : Aucune Date : 13 décembre 2008 Cet examen comprend 5 questions sur 14 pages Répondre sur le cahier de réponse Question 1 Analyse (15 pts) En considérant les récents événements politiques, le Directeur Général des Élections vous approche, vous et votre entreprise, pour faire l'analyse et la conception d'un système de vote électronique qui portera le nom de POLY-VOTE. Suite à des rencontres avec plusieurs intervenants impliqués dans le projet, voici les notes prises par votre équipe sur les différents aspects du futur système de votation : I Configuration matérielle Chacun des bureaux de votes sera équipés de 10 appareils de vote automatiques. Chaque appareil sera relié à un serveur central via une connexion sécurisée. Le serveur central aura accès aux données du Directeur Général des Élections. Les appareils de votes électroniques seront utilisés avant, pendant et après les élections. II Utilisation du système pour la préparation de l'élection Avant le jour de l'élection, différentes tâches devront être effectuées par un administrateur du système pour faire la gestion des partis politiques qui prendront part aux élections. L'administrateur pourra consulter la liste des partis, ajouter un nouveau parti politique autorisé, enlever un parti existant ou modifier les informations sur un parti (le nom du chef,...). III Utilisation du système le jour de l'élection Le jour même de l'élection, les électeurs se rendront à un poste de vote électronique situé dans leur arrondissement pour enregistrer leur vote. Ils pourront alors visualiser la liste des partis politiques inscrits dans leur comté, puis, après s'être dument identifiés, ils pourront voter pour leur parti politique préféré. L'identité de l'électeur sera vérifiée par un Page 1 sur 15
employé à l'aide d'un registre fourni par le Directeur Général des Élections. De plus, pour voter, l'électeur utilisera une carte d électeur qui sera lue par le système. Cette carte d électeur, que l'électeur aura reçue par la poste, comportera un numéro d identification unique. Cette carte sera validée par le Directeur Général des élections (via le serveur central) qui s'assurera que chaque carte n'est utilisée qu'une seule fois. Lorsque la carte aura été validée, le système se déverrouillera et l'électeur pourra entrer son vote en complétant un bulletin que lui aura remis l employé d élection, puis en insérant le bulletin dans le lecteur de l appareil de vote. Ce vote sera enregistré localement et sur le serveur central sans aucune référence à l'électeur, gardant ainsi le vote confidentiel. L appareil de vote produira un son pour confirmer que le vote a bien été enregistré. L'enregistrement et la transmission des bulletins au serveur seront bien entendu encryptés pour en assurer la confidentialité. IV Utilisation du système après le vote Lorsque les bureaux de vote seront officiellement fermés, les scrutateurs utiliseront le système pour comptabiliser les votes. Tout d'abord, une vérification sera faite pour s'assurer que les données locales correspondent à celles présentes sur le serveur central pour détecter une éventuelle fraude. Par la suite, un scrutateur pourra récupérer le résultat du vote. Il aura accès aux données globales, pour chacun des comtés et pour chacun des partis politiques. Sur la base de ces notes, et en vous fiant à votre expertise en matière d'élection, on vous demande de : a) (4 pts) Tracer un diagramme de cas d utilisation de haut niveau (diagramme de contexte) pour le système POLY-VOTE. b) (4 pts) Fournissez une description en format étendu du scénario normal d un électeur qui désire enregistrer son vote pour un parti politique. c) (3 pts) Tracer une première ébauche d architecture logicielle pour le système POLY-VOTE qui respecte les principes d architecture à 3 couches. d) (1 pt) Dans quelle direction les communications doivent-elles circuler dans une architecture multi couches? Utilisez le diagramme fait en c) comme exemple pour illustrer votre réponse. e) (3 pts) Tracer le diagramme de séquence correspondant au cas d utilisation décrit en b). Page 2 sur 15
Réponses: 1-a) Figure 1 : Diagramme de contexte Page 3 sur 15
1-c) Figure 2 : Diagramme de paquetage 1-d) Dans une architecture multi couches, les communications doivent aller des couches supérieures vers les couches inférieures. Idéalement, elles devraient passer par des façades. En b), les éléments de la couche Présentation communiquent directement avec la couche de la logique d application, mais pas l inverse. De même façon, la couche de la logique d application communique avec la couche de sauvegarde et non l inverse. Page 4 sur 15
1-e) Figure 3 : Diagramme de séquence de Voter pour un parti politique Question 2 Conception (17 pts) En considérant la mise en situation de la question précédente, le petit nouveau de votre entreprise vous présente le diagramme de classe suivant pour vous expliquer de quelle façon fonctionneront les communications entre le client et le serveur. Figure 4 : Client-Serveur Vous lui expliquez qu il serait préférable d utiliser le patron Proxy. a) (1 pt) Donnez l intention du patron Proxy Page 5 sur 15
b) (4 pts) Modifiez le diagramme de classe fourni pour y intégrer le patron Proxy et expliquez comment fonctionneront les communications entre le client et le serveur. Questions générales c) ( 9 pts ) Pour chacun des diagrammes de classes ci-dessous, identifiez le nom du patron de conception, son intention et une courte description de son fonctionnement : d) ( 3 pts ) Voici la définition d une classe pour gérer les objets d une scène 3D : class CSceneManager Page 6 sur 15
CSceneManager( void ) ~CSceneManager() void Initialize( void ) void Update( void ) void Render( void ) void Clear( void ) void AddObject( CSceneObject* obj ) void RemoveObject( const int i ) private: vector<csceneobject*> m_sceneobjects; Sachant que cette classe doit être unique, ré implémentez la en forme de singleton et donner l intention de ce patron. Notez bien qu on ne doit pas être en mesure de la copier aussi. Réponses : 2-a) Fournir un remplaçant ou une doublure pour un autre objet afin de contrôler l accès à ce dernier. 2-b) Les communications entre le client et le serveur se font via les proxys. Le client interroge sont Proxy_Serveur comme s il s agissait d un serveur local. Ce dernier est responsable de transmettre les requêtes au Serveur_POLY_VOTE. Le Proxy_Serveur sait comment accéder au serveur qu il soit local ou distant. Si le type de communication change, il suffit de remplacer le Proxy_Serveur. L impact sur le client est ainsi minimiser. Page 7 sur 15
Figure 5 :Application du patron proxy 2c 1 ) Patron Chain of Responsability Intention : Éviter de coupler l émetteur et le récepteur d une requête en donnant la possibilité à plus d un objets de traiter la requête. 2 ) Patron Composite Intention : Traiter les objets individuels et les objets multiples, composés récursivement, de façon uniforme. 3 ) Patron Abstract Factory Intention : Procure une interface permettant de créer une famille d objets connexes ou dépendants sans spécifier leurs classes concrètes. 2d class CSceneManager static CSceneManager* GetInstance( void ) if( m_instance == NULL ) m_instance = new CSceneManager(); return m_instance; void Initialize( void ) void Update( void ) void Render( void ) void Clear( void ) void Free( void ) Page 8 sur 15
delete m_instance; m_instance = NULL; void AddObject( CSceneObject* obj ) void RemoveObject( const int i ) private: static CSceneManager* m_instance; CSceneManager( void ) ~CSceneManager( void ) Free(); CSceneManager ( const CSceneManager & ); CSceneManager& operator = ( const CSceneManager& ); vector<csceneobject*> m_sceneobjects; // Dans le *.cpp CSceneManager* CSceneManager::m_Instance = NULL; Page 9 sur 15
Question 3 Héritage et polymorphisme (10 pts) a) (1,5 pts) Quelle est la différence entre une fonction virtuelle, une fonction virtuelle pure et une fonction non-virtuelle? b) (3 pts) Que sera-t-il affiché à l écran suite à l exécution du programme suivant : #include <iostream> using namespace std; class A A( void ) cout << "A::A()" << endl; virtual ~A() cout << "A::~A()" << endl; void methode1( void ) cout << "A::methode1()" << endl; virtual void methode2( void ) cout << "A::methode2()" << endl; virtual void methode3( void )=0 cout << "A::methode3()" << endl; class B : public A B( void ) : A() cout << "B::B()" << endl; virtual ~B() cout << "B::~B()" << endl; void methode3( void ) cout << "B::methode3()" << endl; virtual void methode4( void ) cout << "B::methode4()" << endl; class C : public B C( void ) : B() cout << "C::C()" << endl; virtual ~C() cout << "C::~C()" << endl; void methode1( void ) cout << "C::methode1()" << endl; void methode2( void ) cout << "C::methode2()" << endl; void methode3( void ) cout << "C::methode3()" << endl; void methode5( void ) cout << "C::methode5()" << endl; int main( int argc, char** argv ) B b; C c; A* pab = &b; A* pac = &c; pab->methode1(); pac->methode1(); cout << endl; Page 10 sur 15
pab->methode2(); pac->methode2(); cout << endl; pab->methode3(); pac->methode3(); cout << endl; return 0; c) (1.5 pts) Donnez les tables virtuelles des classes A, B et C d) (1 pt) Que faut-il constater en tant que programmeur lorsque le destructeur d une classe n est pas virtuel? Pourquoi? e) (3 pts) Si nous ajoutons les classes suivantes au projet courant : class D D( void ) virtual ~D() virtual void methode3() cout << "D::methode3()" << endl; class E : public C, public D E( void ) : C(), D() virtual ~E() Expliquez l erreur obtenue par la ligne suivante : E e; cout << e.methode3() << endl; Que faut-il faire pour être en mesure de surcharger la fonction methode3() dans la classe E? Bref nous aimerions pouvoir conserver le polymorphisme est utiliser la classe E comme telle : E e; A* pae = &e; D* pde = &e; pae->methode3(); pde->methode3(); Écrivez le code qui nous permettra d obtenir le fonctionnement désiré. Page 11 sur 15
Réponses : 3a Méthode virtuelle: méthode pouvant être surchargée dans les classes enfant Méthode virtuelle pure: méthode devant être surchargées dans les classes enfant Méthode non-virtuelle: méthode ne pouvant pas être surchargée 3b A::A() B::B() A::A() B::B() C::C() A::methode1() A::methode1() A::methode2() C::methode2() B::methode3() C::methode3() C::~C() B::~B() A::~A() B::~B() A::~A() 3c Tables virtuelles : Offset Classe A Classe B Classe C 0 A ::~A() B ::~B() C ::~C() 4 A ::methode2() A ::methode2() C ::methode2() 8 NULL B ::methode3() C ::methode3() 12 B ::methode4() B ::methode4() 3d Cette classe n a pas été conçue pour être héritée et utilisée de façon polymorphique. Le compilateur se base sur le type du pointeur et non sur l objet pointé. Donc, si nous gérons notre objet dérivé avec un pointeur de base, seul le destructeur de la classe de base sera appelé. 3e 1) Compilateur génére une erreur car l appel à la fonction est ambigue. 2) class CAux : public C CAux() : C() Page 12 sur 15
virtual ~CAux() void methode3() Cmethode3(); virtual void Cmethode3() = 0; class DAux : public D DAux() : D() virtual ~DAux() void methode3() Dmethode3(); virtual void Dmethode3() = 0; class E : public CAux, public DAux E( void ) : CAux(), DAux() virtual ~E() void Cmethode3() cout << "E::Cmethode3()" << endl; void Dmethode3() cout << "E::Dmethode3()" << endl; Question 4 Gestion des exceptions (3 pts) a) (1,5 pts) Voici trois déclarations de fonction différentes : void methode1( void ) throw "Exception methode1()"; void methode2( void ) throw() throw "Exception methode2()"; void methode3( void ) throw( int ) throw "Exception methode3()"; Pour chacune expliquez la suite du déroulement de l application si elle lance son exception. b) (0,5 pt) Quelle est l affichage du programme suivant : #include <iostream> #include <exception> using namespace std; class A A( void ) throw() ~A() throw "A::~A()"; class B B( void ) : m_a() throw "B::B()"; Page 13 sur 15
~B() throw() A m_a; void NewTerminate() cout << "Nouveau Terminate!" << endl; abort(); void NewUnexpected() cout << "Nouveau Unexpected!" << endl; int main( int argc, char** argv ) set_unexpected( NewUnexpected ); set_terminate( NewTerminate ); try B b; catch( char* exception ) cout << "Exception : " << exception << endl; catch(... ) cout << "On attrape le reste!" << endl; return 0; c) (1 pt) Selon vous a-t-il une meilleure façon de gérer les exceptions dans le code présenté en b)? Pourquoi? Réponses : 4a Methode1 remonte la pile des appels jusqu à l endroit ou cette erreur peut être traitée. S il n y a pas de catch correspondant terminate() appelé. Methode2 est déclarée avec une liste vide donc unexpected() sera appelé suivi de terminate() Methode3 réagira comme methode2 car sa liste contient seulement le type int. Page 14 sur 15
4b Nouveau Terminate! 4c - Ne jamais lancer d exception dans un destructeur car on ne sait pas quand il sera appeler soit lors de sa destruction normale ou sinon en remontant la pile lors du traitement d une exception. Selon le standard c++ terminate sera invoqué si deux exceptions simultanées sont lancées. - Si c est absolument nécessaire de lancer l exception dans le destructeur, une solution possible serait de l avalé avec catch( ) puis écrire l exception dans un log. Question Bonus ( 1 pt ) Soit le code suivant : for( int i = 0; i < 100; i++ ) if(???????????? ) cout << i << endl; Écrire la condition du if pour afficher seulement les chiffres de puissance de 2. Réponse :!( i & ( i - 1 ) ) Page 15 sur 15