420-202-RE – Structures de données et programmation orientée objet

Quelques raccourcis :

Ceci est un petit site de support pour le cours 420-202-RE – Structures de données et programmation orientée objet.

Vous trouverez aussi des liens sur divers langages (dont C#, notre outil de prédilection dans ce cours) un peu partout dans http://h-deb.clg.qc.ca/

Les diverses sections de cette page (en fonction desquelles vous trouverez quelques liens dans l'encadré à droite) vous mèneront elles-aussi sur des pistes qui vous permettront d'explorer un peu plus par vous-mêmes, de valider vos acquis et d'enrichir votre apprentissage

Notez qu'une partie de cette page s'adresse aux étudiantes et aux étudiants de tous les groupes de 420-202 (la section listant les énoncés de laboratoires et les dates importantes de la session, du point de vue de ce cours bien sûr), alors que le reste est surtout destiné aux étudiantes et aux étudiants de mon cours (exemples, semainier, résultats des travaux pratiques, etc.).

Cela dit, si vous suivez ce cours avec un autre enseignant que moi, vous êtes quand même les bienvenus ici, bien qu'il vous faille retenir que si votre enseignant et moi nous contredisons (et oui, ça arrive!), alors de votre point de vue, c'est votre enseignant qui a raison, pas moi

Détail des séances en classe

Date Séance Détails
21 février L00

Au menu :

  • présentation du cours et du plan de cours;
  • mise en place du vocabulaire de base de la POO, mais sous forme d'un survol seulement, et avec accent sur l'encapsulation. Ainsi, nous avons identifié et situé les termes et idées suivant(e)s :
    • respect des invariants, entre chaque appel d'un service d'un objet;
    • identification et garantie du respect des préconditions pour les services d'un objet;
    • identification et garantie du respect des postconditions pour les services d'un objet;
    • une maxime importante : « un objet est responsable de son intégrité, du début à la fin de sa vie »;
    • quelques mots clés qui aident à encadrer la capacité d'un objet à assurer son encapsulation, soit les qualifications d'accès private, protected, public;
      • nous n'avons pas vraiment parlé de protected, qui a une utilité réelle mais plus limitée que des deux autres, et dont nous ne pouvons pas traiter pour le moment;
      • nous avons toutefois insisté sur le fait que private, la qualification par défaut des membres dans une classe, est ce que nous souhaitons utiliser le plus possible, et nous avons donné quelques raisons pour soutenir cette prise de position;
      • nous avons démontré que public, du moins pour les états d'un objet, devrait être évité dans presque tous les cas, cette qualification empêchant de facto l'objet d'assurer son encapsulation;
  • nous avons mise de l'avant quelques mots de vocabulaire plus techniques, soit :
    • les méthodes, qui sont les services (les fonctions, les comportements) d'un objet;
    • les attributs, qui sont les états (variables, constantes) d'un objet;
    • les constructeurs, qui sont les méthodes un peu spéciales mais Ô combien importantes qui permettent de déterminer l'état initial d'un objet – sans eux, pas d'état initial connu pour un objet, donc pas d'encapsulation pour lui;
  • nous avons discuté du glissement sémantique entre f(obj), où on applique une opération sur une entité , ce qui rejoint l'approche procédurale que nous avons mis en application dans le cours 420-201-RE, et obj.f(), qui sollicite la méthode de l'objet et rend ce dernier actif;
  • nous avons discuté brièvement des accesseurs (méthodes de consultation) et des mutateurs (méthodes de modification), qui sont des familles de services typiques pour les objets; et
  • nous avons montré que C# permet une écriture concise pour ces deux familles importantes de services, soit les propriétés, et nous avons indiqué qu'il s'agit d'une pratique idiomatique dans ce langage, donc d'une pratique que nous allons mettre en application même si elle est plus « décorative » que nécessaire;
  • enfin, nous avons écrit une petite classe Carré telle que le programme suivant :
// ...
static void Main()
{
   Carré c = new Carré(3);
   c.Dessiner();
}
// ...
  • affichera à la console la chose suivante :
###
###
###
  • ... et nous avons choisi de faire en sorte que la taille d'un côté de Carré soit 21, de manière « scientifique » (nous étions le 21 février, alors voilà!).
26 février T00

Au menu :

  • petite séance de programmation collective. Écrivons ensemble un programme qui :
    • lit un nombre de personnes (entier strictement positif);
    • pour chaque personne :
      • lit son nom;
      • lit son âge;
      • l'ajoute dans un tableau;
  • une fois toutes les personnes lues :
    • trouver et afficher le nom de la personne dont le nom est le plus long;
    • afficher l'âge moyen des personnes (si l'âge d'une personne est nul, nous ne le considérerons pas).

Pour y arriver, nous définirons une classe Personne telle que :

  • une Personne aura un nom et un âge;
  • le nom d'une Personne ne pourra être vide (par défaut, nous utiliserons le nom "INCONNU(E)";
  • l'âge d'une Personne devra se situer entre 0 et 140 inclusivement (par défaut, nous considérerons qu'une Personne est d'âge zéro).

Une solution possible est disponible ici.

28 février L01

Au menu :

  • gros cours (ce ne sera pas toujours aussi rude, ne vosu en faites pas), avec deux grandes thématiques :
  • les exceptions, qui permettent entre autres de découpler la détection d'une situation atypique (une erreur, dans la grande majorité des cas) de son traitement; et
  • l'héritage, effleuré seulement, par lequel une classe peut être une spécialisation d'une autre.

Voir ceci pour des détails.

5 mars T01

Au menu :

7 mars L02

Au menu :

12 mars T02

Au menu :

  • solutions possibles aux exercices 1a, 1b et 2;
  • conversion de char à int et inversement;
  • écrire un programme de test pour une classe (bref);
  • si le temps le permet, travail sur le laboratoire 1;
  • exercice facultatif et formatif pour qui aura terminé le laboratoire 1.

Si vous le souhaitez, vous trouverez ici une archive ZIP contenant un projet C# graphique, dans lequel se trouve une classe Cryptographie vide. Si votre travail est fait dans le respect des exigences, vous devriez pouvoir copier le code de votre classe Cryptographie, le coller dans la classe « vide » de ce projet, et tout devrait fonctionner.

14 mars L03

Au menu :

  • remise du laboratoire 1;
  • déroulement d'un programme :
    • ordre dans lequel surviennent les étapes de l'exécution;
    • rôle code client;
    • rôle des objets;
    • rôle des fonctions;
  • modularisation :
    • classes;
    • instances;
    • membres d'instance et membres de classe :
      • attributs;
      • méthodes;
        • fonctions, méthodes de classe et abus de nomenclature;
      • constructeurs;
  • relations entre objets (première approche) :
    • association;
    • composition;
    • agrégation;
  • quelques très petits éléments de notation UML :
    • la boîte décrivant une classe ou une de ses instances;
    • le + pour un membre public;
    • le - pour un membre privé;
    • le nom de la classe;
    • les noms des attributs;
    • les signatures des méthodes; et
    • le soulignement pour les membres de classe.

Les thématiques de ce matin ont été, du moins en partie, illustrées par un programme mettant en relief la tension entre le bien et le mal.

19 mars T03

Au menu : cours sans votre chic prof, dont le bébé Ludo est né le 18 mars 2013. Il est possible que Pierre Prud'homme, illustre collègue et chic type devant l'éternel, me remplace pour cette séance, mais je ne contrôle pas cet aspect de la situation alors vous en savez sans doute plus que moi ici.

Utilisez ce temps pour travailler sur le laboratoire 2, pour lequel un énoncé en format PDF et une archive ZIP contenant un programme incomplet vous ont été livrés par Colnet.

21 mars L04

Au menu : cours sans votre chic prof, dont le bébé Ludo est né le 18 mars 2013. Il est possible que Pierre Prud'homme, illustre collègue et chic type devant l'éternel, me remplace pour cette séance, mais je ne contrôle pas cet aspect de la situation alors vous en savez sans doute plus que moi ici.

Utilisez ce temps pour travailler sur le laboratoire 2, pour lequel un énoncé en format PDF et une archive ZIP contenant un programme incomplet vous ont été livrés par Colnet.

26 mars T04

Au menu :

  • terminer le laboratoire 2, si ce n'est fait, et le remettre en version imprimée à votre chic prof;
  • poser toutes les questions qui vous turlupinent jusqu'ici.
28 mars L05

Au menu :

  • minitest #1.

Suite au minitest, votre chic prof a choisi de vous laisser aller (n'ayant pas discuté avec son collègue du fait de donner un non un cours d'une heure entre un test et les « vacances » de Pâques, si brèves soient-elles), mais a laissé pour vous au tableau cette piste de réflexion :

« Lors de la séance L03, le 14 mars (jour de ), vous avez été confronté(e)s à un combat ÉPIQUE entre le bien et le mal.

Vous remarquerez toutefois des similitudes structurelles et comportementales entre le Héros et le Monstre.

Comment pourrions-nous réorganiser ce programme pour tirer profiter de ce que ces entités ont de commun ou de semblable? »

2 avril T05

Au menu :

  • retour sur le minitest #1;
  • retour sur le laboratoire 1;
  • retour sur le laboratoire 2;
  • plusieurs petits trucs :
    • comment nommer une propriété et comment nommer une méthode;
    • quand initialiser un attribut d'instance;
    • qu'est-ce que le principe de localité et pourquoi l'appliquer religieusement;
  • conception d'une tableau dynamique, offrant les services suivants :
    • construction par défaut d'un tableau vide (capacité nulle);
    • construction paramétrique d'un tableau de capacité initiale supérieure ou égale à zéro;
    • méthodes et propriétés clés :
      • consulter le nombre d'éléments dans le tableau;
      • constater que le tableau est vide ou non;
      • accès en consultation à un élément étant donné sa position dans le tableau;
      • ajout d'un élément au début du tableau;
      • ajout d'un élément à la fin du tableau;
      • insertion d'un élément avant une position donnée dans le tableau;
      • suppression de l'élément au début du tableau;
      • suppression de l'élément à la fin du tableau;
      • suppression de l'élément à une position donnée dans le tableau;
      • compaction automatique lorsqu'au moins du tableau est inutilisé;
  • détails d'implémentation :
    • distinction entre capacité et nombre d'éléments;
    • méthode de croissance;
    • méthode de compaction;
    • taux d'occupation;
  • considérations de design :
    • identification des préconditions des divers services;
    • identification des postconditions des divers services;
    • identificiation des invariants de le classe;
    • maximiser la réutilisation des services;
    • distinguer services privés et publics.

Pour un bref de ce que nous avons fait avant de fermer boutique aujourd'hui, voir ceci. Je vous invite fortement à poursuivre le travail d'ici le prochain cours.

4 avril  

Pas de cours aujourd'hui; c'est un jeudi qui se déguise en lundi

9 avril T06

Au menu :

11 avril L06

Au menu :

  • un regard neuf sur le le design des classes... Gros cours, où nous avons présenté :
    • l'héritage;
    • le polymorphisme; et
    • l'abstraction.
  • Ces trois thèmes sont, avec l'encapsulation, parmi les principaux piliers de la POO.

Compte-tenu de la densité de la matière couverte aujourd'hui, il est normal que les idées se bousculent dans vos têtes. Heureusement, nous allons revenir encore et encore sur le sujet, alors « ¸ça devrait finir par se placer ». D'ici là, l'exemple de formes avec héritage, polymorphisme et abstraction que nous avons fait en classe apparaît ici.

16 avril T07

Au menu :

  • examen intra.
18 avril L07

Au menu :

  • retour sur l'examen intra;
  • retour sur le laboratoire 3;
  • une question clé : un Carré est-il une sorte de Rectangle?
  • exercice pratique formatif :
    • on envisage créer un logiciel de gestion de pool de hockey;
    • dans notre pool, une équipe peut être constituée de dix joueurs maximum;
    • une équipe a un nom;
    • un joueur peut être un avant, un défenseur ou un gardien;
    • tout joueur a un nom;
    • un avant a des buts et des passes;
    • un défenseur a des buts et des passes;
    • un gardien a des victoires, des défaites, des défaites en fusillade et des blanchissages;
    • le nombre de blanchissages d'un gardien ne peut excéder son nombre de victoires;
    • toute équipe doit avoir entre un et deux gardiens inclusivement;
    • toute équipe doit avoir au moins trois défenseurs;
    • les points d'une équipe sont la somme des points de ses joueurs;
    • un avant a deux points par but et un point par passe;
    • un défenseur a un point par but et deux points par passe;
    • un gardien a trois points par victoire, deux points supplémentaires par blanchissage, et un point par défaite en fusillade;
    • afficher une équipe signifie afficher son nom, puis afficher les noms, position (avant, défenseur, gardien) et points de chacun de ses joueurs, de même que le total des points des joueurs de cette équipe;
    • le pool contient au plus six équipes;
    • afficher un pool signifie afficher les équipes en ordre décroissant de pointage et indiquer quelle équipe est en tête.
23 avril T08

Au menu :

25 avril L08

Au menu :

30 avril T09

Au menu :

  • journée d'encadrement et de supervision.
2 mai L09

Au menu :

  • introduction aux interfaces avec C#; et
  • travail sur le laboratoire 4.
7 mai T10

Au menu :

  • interfaces avec C#, quelques exemples supplémentaires;
  • cycle de vie d'un objet;
  • bases de notation UML.
9 mai L10

Au menu :

14 mai T11

Au menu :

  • surcharge des opérateurs;
  • travail sur le laboratoire 5.

Pour présenter la surcharge des opérateurs en C#, j'ai « inventé » avec vous une classe Rationnel. Vous en trouverez une semblable ici, si jamais la chose vous intéresse.

16 mai L11

Au menu :

  • contenu exact à déterminer;
  • travail sur le laboratoire 5.
21 mai T12

Au menu :

  • minitest #2.
23 mai L12

Au menu :

  • retour sur le minitest #2;
  • remettre le laboratoire 5 en version imprimée à votre chic prof;
  • distribution de l'activité synthèse du cours;
  • travail sur l'activité synthèse.
28 mai T13

Au menu :

  • travail sur l'activité synthèse.
30 mai L13

Au menu :

  • journée d'encadrement et de supervision.
4 juin T14

Au menu :

  • travail sur l'activité synthèse.
6 juin L14

Examen final

Documents sous forme électronique

Cliquez sur cette cible pour le plan de cours, sous forme électronique

Cliquez sur cette cible pour un résumé des principales règles de programmatique en vigueur dans ce cours.

Petits coups de pouces

Vous trouverez ici quelques documents, la plupart petits, qui peuvent vous donner un petit coup de pouce occasionnel.

Télécharger la version gratuite de Visual Studio 2010 permettant de programmer en C# de la maison. Il y a plusieurs liens sur cette page, mais celui que vous souhaitez utiliser est pour Visual C# 2010 Express.

Comment accéder à du code .NET à partir d'un client C++ natif.

Vous trouverez aussi des exemples de code C# dans la section Divers – C# du site, mais notez que je n'ai pas nécessairement harmonisé ces exemples (écrits pour des cours plus avancés, sous forme de survols) aux standards de programmation appliqués dans le présent cours. À lire avec prudence et discrimination, donc.

Solutionnaires

Quelques solutionnaires suivent. Référez-vous aux règles programmatiques pour plus de détails sur les normes de programmation appliquées dans ce cours.

En espérant que ça vous aide à organiser vos idées!

ExempleSéance
Classe Carré L00
Classe Personne T00
Classe Point et introduction aux exceptions L01

Programme de test – Exercice 1a

L'exercice 1a porte sur une classe Point générale dont chaque instance représente un point dans n'importe quel quadrant du plan cartésien. Un programme de test pour cet exercice suit.

//---------------------------------------------------------
// Programme de test de la classe Point
// 
// Ce programme vérifie que la classe Point fait 
// correctement son travail
//
// par Pierre Prud'homme, 2013
//---------------------------------------------------------
using System;

namespace _03_Exercice1a
{
   class ProgrammeTestExercice1a
   {
      static void Main(string[] args)
      {
         // test du constructeur par défaut
         Point pDéfaut = new Point();
         AfficherPoint("Test du constructeur par défaut", pDéfaut);
         AfficherSéparateur();
         
         // test du constructeur paramétrique
         Point pQuadrant1 = new Point(1.1f, 1.1f);
         Point pQuadrant2 = new Point(-2.2f, 2.2f);
         Point pQuadrant3 = new Point(-3.3f, -3.3f);
         Point pQuadrant4 = new Point(4.4f, -4.4f);

         AfficherPoint("Test du constructeur paramétrique pour Q1", pQuadrant1);
         AfficherSéparateur();
         AfficherPoint("Test du constructeur paramétrique pour Q2", pQuadrant2);
         AfficherSéparateur();
         AfficherPoint("Test du constructeur paramétrique pour Q3", pQuadrant3);
         AfficherSéparateur();
         AfficherPoint("Test du constructeur paramétrique pour Q4", pQuadrant4);
         AfficherSéparateur();

         // test du mutateur de x et de y
         pQuadrant1.X = 10.10f;
         pQuadrant1.Y = 10.10f;
         AfficherPoint("Test du mutateur de Q1", pQuadrant1);
         AfficherSéparateur();
      }

      static void AfficherPoint(string message, Point p)
      {
         Console.WriteLine("{0} : le point vaut [{1}, {2}]", message, p.X, p.Y);
      }

      static void AfficherSéparateur()
      {
         const int NB_CAR_LIGNE = 72;
         string s = new string('-', NB_CAR_LIGNE);
         Console.WriteLine();
         Console.WriteLine(s);
         Console.WriteLine();
      }
   }
}

Programme de test – Exercice 1b

L'exercice 1a porte sur une classe PointQuadrant1 générale dont chaque instance représente un point situé dans le premier quadrant du plan cartésien. Un programme de test pour cet exercice suit.

//---------------------------------------------------------
// Programme de test de la classe PointQuadrant1
// 
// Ce programme vérifie que la classe Point fait 
// correctement son travail
//
// par Pierre Prud'homme, 2013
//---------------------------------------------------------
using System;

namespace _03_Exercice1b
{
   class ProgrammeTestExercice1a
   {
      static void Main(string[] args)
      {
         TesterConstructeurParamétrique();
         TesterMutateurs();
      }

      static void AfficherPoint(string message, PointQuadrant1 p)
      {
         Console.WriteLine("{0} :", message);
         Console.WriteLine("Le point vaut [{0}, {1}]", p.X, p.Y);
      }

      static void AfficherSéparateur()
      {
         const int NB_CAR_LIGNE = 72;
         string s = new string('-', NB_CAR_LIGNE);
         Console.WriteLine();
         Console.WriteLine(s);
         Console.WriteLine();
      }

      static void TesterConstructeurParamétrique()
      {
         // tests du constructeur paramétrique
         TesterPoint(1.1f, 1.1f);
         TesterPoint(-2.2f, 2.2f);
         TesterPoint(-3.3f, -3.3f);         
         TesterPoint(4.4f, -4.4f);
      }

      static void TesterPoint(float x, float y)
      {
         Console.WriteLine("Point reçu : [{0}, {1}]", x, y);
         try
         {
            PointQuadrant1 p = new PointQuadrant1(x, y);
            AfficherPoint("Test réussi du constructeur paramétrique pour PointQuadrant1", p);
         }
         catch (CoordonnéeXInvalideException)
         {
            Console.WriteLine("Point invalide en x lors de la construction");
         }
         catch (CoordonnéeYInvalideException)
         {
            Console.WriteLine("Point invalide en y lors de la construction");
         }
         AfficherSéparateur();
      }

      static void TesterMutateurs()
      {
         PointQuadrant1 p = new PointQuadrant1(0, 0);

         ModifierPoint(p, 11.0f, p.Y);
         ModifierPoint(p, p.X, 11.0f);
         ModifierPoint(p, 111.0f, 111.0f);
         ModifierPoint(p, -22.2f, p.Y);
         ModifierPoint(p, p.X, -22.2f);
         ModifierPoint(p, -222.0f, -222.0f);
      }

      static void ModifierPoint(PointQuadrant1 p, float x, float y)
      {
         Console.WriteLine("Modification du point reçue : [{0}, {1}]", x, y);
         try
         {
            p.X = x;
            p.Y = y;
            Console.WriteLine("Test réussi de la mutation du point");
         }
         catch (CoordonnéeXInvalideException)
         {
            Console.WriteLine("Point invalide en x lors de la modification");
         }
         catch (CoordonnéeYInvalideException)
         {
            Console.WriteLine("Point invalide en y lors de la modification");
         }
         AfficherPoint("Après mutation ", p);
         AfficherSéparateur();
      }
   }
}

Exercice 1a – Solution possible

Une solution possible à l'exercice 1a serait la suivante :

// ...
class Point
{
   const float X_DÉFAUT = 0.0f,
               Y_DÉFAUT = 0.0f;
   float x,
         y;
   public float X
   {
      get { return x; }
      set { x = value; }
   }
   public float Y
   {
      get { return y; }
      set { y = value; }
   }
   public Point()
   {
      X = X_DÉFAUT;
      Y = Y_DÉFAUT;
   }
   public Point(float valX, float valY)
   {
      X = valX;
      Y = valY;
   }
}
// ...

Si l'énoncé n'avait pas spécifiquement demandé d'utiliser des attributs réels x et y, il aurait été possible ici d'utiliser des propriétés dites « automatiques » du fait que celles-ci ne font aucune validation sur les valeurs qui leur sont affectées. On aurait alors eu :

// ...
class Point
{
   const float X_DÉFAUT = 0.0f,
               Y_DÉFAUT = 0.0f;
   public float X
   {
      get;
      set;
   }
   public float Y
   {
      get;
      set;
   }
   public Point()
   {
      X = X_DÉFAUT;
      Y = Y_DÉFAUT;
   }
   public Point(float valX, float valY)
   {
      X = valX;
      Y = valY;
   }
}
// ...

Il est rare en pratique que nos classes n'aient aucun invariant à valider, ce qui explique que nous mettions l'accent sur la validation des attributs et que, dans le code que vous rencontrerez dans ce cours, les propriétés automatiques soient somme toute assez rares.

Exercice 1b – Solution possible

Une solution possible à l'exercice 1b serait la suivante :

// ...
class CoordonnéeXInvalideException : ApplicationException
{
}
class CoordonnéeYInvalideException : ApplicationException
{
}
class PointQuadrant1
{
   const float X_MIN = 0.0f,
               Y_MIN = 0.0f;
   float x,
         y;
   public float X
   {
      get { return x; }
      set
      {
         if (value < X_MIN)
         {
            throw new CoordonnéeXInvalideException();
         }
         x = value;
      }
   }
   public float Y
   {
      get { return y; }
      set
      {
         if (value < Y_MIN)
         {
            throw new CoordonnéeYInvalideException();
         }
         y = value;
      }
   }
   public PointQuadrant1(float valX, float valY)
   {
      X = valX;
      Y = valY;
   }
}
// ...

Cet énoncé ne demandait pas que la classe PointQuadrant1 expose un constructeur par défaut. Cela dit, je n'ai pas pénalisé celles et ceux qui ont choisi d'en offrir un, qui construirait un PointQuadrant1 à {0,0}, cette description d'un point par défaut me semblant raisonnable ici.

Exercice 2 – Solution possible

L'exercice 2 implique de rédiger une classe CompteBancaire et un petit programme de test pour valider le fruit de votre labeur.

La classe CompteBancaire et les classes d'exceptions exigées pourraient s'exprimer ainsi :

using System;
namespace Laboratoire2
{
   class SoldeInvalideException : ApplicationException
   {
   }
   class OpérationInvalideException : ApplicationException
   {
   }
   class CompteBancaire
   {
      const int SOLDE_DÉFAUT = 0;
      //
      // J'ai utilisé une propriété automatique, mais¸on
      // aurait aussi pu utiliser une propriété encapsulant
      // les accès à un attribut privé
      //
      public int Solde
      {
         get;
         private set;
      }
      public void Déposer(int montant)
      {
         if (montant <= 0)
         {
            throw new OpérationInvalideException();
         }
         Solde += montant;
      }
      public void Retirer(int montant)
      {
         if (montant <= 0 || Solde < montant)
         {
            throw new OpérationInvalideException();
         }
         Solde -= montant;
      }
      public CompteBancaire()
      {
         Solde = SOLDE_DÉFAUT;
      }
      public CompteBancaire(int montantInitial)
      {
         if (montantInitial < 0)
         {
            throw new SoldeInvalideException();
         }
         Solde = montantInitial;
      }
   }
   // ...

Un programme de test pour cette classe pourrait être :

   // ...
   class Program
   {
      static void AfficherSéparateur()
      {
         Console.WriteLine(new string('-', 75));
      }
      static void TestCréationCompte(int montantInitial, string attendu)
      {
         AfficherSéparateur();
         Console.WriteLine("Tentative de création d'un compte avec solde initial à {0}", montantInitial);
         Console.Write("Attendu: {0}... Obtenu: ", attendu);
         try
         {
            CompteBancaire cb = new CompteBancaire(montantInitial);
            Console.WriteLine("Solde du compte nouvellement créé: {0}", cb.Solde);
         }
         catch (SoldeInvalideException)
         {
            Console.WriteLine("Solde invalide à la construction");
         }
      }
      static void TestsCréation()
      {
         //
         // Compte par défaut
         //
         CompteBancaire cb = new CompteBancaire();
         Console.WriteLine("Compte bancaire par défaut. Solde attendu: 0. Solde obtenu: {0}", cb.Solde);
         //
         // Comptes paramétriques
         //
         TestCréationCompte(0, "Solde nul");
         TestCréationCompte(100, "Solde de 100");
         TestCréationCompte(-100, "Exception");
      }
      static void TestDépôt(CompteBancaire cb, int montant, string attendu)
      {
         AfficherSéparateur();
         Console.WriteLine("Tentative de dépôt de {0}$ dans un compte avec solde initial à {1}", montant, cb.Solde);
         Console.Write("Attendu: {0}... Obtenu: ", attendu);
         try
         {
            cb.Déposer(montant);
            Console.WriteLine("Solde après dépôt: {0}", cb.Solde);
         }
         catch (OpérationInvalideException)
         {
            Console.WriteLine("Opération invalide");
         }
      }
      static void TestRetrait(CompteBancaire cb, int montant, string attendu)
      {
         AfficherSéparateur();
         Console.WriteLine("Tentative de retrait de {0}$ dans un compte avec solde initial à {1}", montant, cb.Solde);
         Console.Write("Attendu: {0}... Obtenu: ", attendu);
         try
         {
            cb.Retirer(montant);
            Console.WriteLine("Solde après retrait: {0}", cb.Solde);
         }
         catch (OpérationInvalideException)
         {
            Console.WriteLine("Opération invalide");
         }
      }
      static void TestsOpérations()
      {
         CompteBancaire cb = new CompteBancaire();
         TestDépôt(cb, 100, "Solde de 100");
         TestDépôt(cb, 0, "Exception");
         TestDépôt(cb, -3, "Exception");
         TestDépôt(cb, 50, "Solde de 150");
         TestRetrait(cb, 151, "Exception");
         TestRetrait(cb, 140, "Solde de 10");
         TestRetrait(cb, 0, "Exception");
         TestRetrait(cb, -1000, "Exception");
      }
      static void Main(string[] args)
      {
         TestsCréation();
         TestsOpérations();
      }
   }
}

Vous remarquerez que, un peu comme en 420-201, ce programme teste chaque méthode avec des cas prévisibles et des cas limites, présentant à la fois le résultat attendu et le résultat obtenu.

La sortie à l'écran de ce programme de test est :

Compte bancaire par défaut. Solde attendu: 0. Solde obtenu: 0
---------------------------------------------------------------------------
Tentative de création d'un compte avec solde initial à 0
Attendu: Solde nul... Obtenu: Solde du compte nouvellement créé: 0
---------------------------------------------------------------------------
Tentative de création d'un compte avec solde initial à 100
Attendu: Solde de 100... Obtenu: Solde du compte nouvellement créé: 100
---------------------------------------------------------------------------
Tentative de création d'un compte avec solde initial à -100
Attendu: Exception... Obtenu: Solde invalide à la construction
---------------------------------------------------------------------------
Tentative de dépôt de 100$ dans un compte avec solde initial à 0
Attendu: Solde de 100... Obtenu: Solde après dépôt: 100
---------------------------------------------------------------------------
Tentative de dépôt de 0$ dans un compte avec solde initial à 100
Attendu: Exception... Obtenu: Opération invalide
---------------------------------------------------------------------------
Tentative de dépôt de -3$ dans un compte avec solde initial à 100
Attendu: Exception... Obtenu: Opération invalide
---------------------------------------------------------------------------
Tentative de dépôt de 50$ dans un compte avec solde initial à 100
Attendu: Solde de 150... Obtenu: Solde après dépôt: 150
---------------------------------------------------------------------------
Tentative de retrait de 151$ dans un compte avec solde initial à 150
Attendu: Exception... Obtenu: Opération invalide
---------------------------------------------------------------------------
Tentative de retrait de 140$ dans un compte avec solde initial à 150
Attendu: Solde de 10... Obtenu: Solde après retrait: 10
---------------------------------------------------------------------------
Tentative de retrait de 0$ dans un compte avec solde initial à 10
Attendu: Exception... Obtenu: Opération invalide
---------------------------------------------------------------------------
Tentative de retrait de -1000$ dans un compte avec solde initial à 10
Attendu: Exception... Obtenu: Opération invalide

Exercice facultatif – séance T02

Si vous le souhaitez, vous pouvez faire l'exercice suivant. Étant donné les classes Point et Cercle ci-dessous, de même que les classes d'exceptions qui les accompagnent, écrivez un programme de test pour valider que la méthode Intersecte() de la classe Cercle fonctionne correctement. La méthode DistanceDe() implémente la bien connue équation .

//
// Point
//
class Point
{
   const float X_DÉFAUT = 0.0f,
               Y_DÉFAUT = 0.0f;
   public float X
   {
      get;
      set;
   }
   public float Y
   {
      get;
      set;
   }
   public Point()
   {
      X = X_DÉFAUT;
      Y = Y_DÉFAUT;
   }
   public Point(float valX, float valY)
   {
      X = valX;
      Y = valY;
   }
   public float DistanceDe(Point p)
   {
      //
      // Pythagore
      //
      return (float) Math.Sqrt(Math.Pow(X - p.X, 2.0f) + Math.Pow(Y - p.Y, 2.0f));
   }
}
//
// Cas d'exceptions possibles pour un Cercle
//
class RayonInvalideException : ApplicationException
{
}
class PointInvalideException : ApplicationException
{
}
//
// Cercle
//
class Cercle
{
   const float RAYON_DÉFAUT = 1.0f;
   Point centre;
   float rayon;
   public Point Centre
   {
      get { return centre; }
      private set
      {
         if (value == null)
         {
            throw new PointInvalideException();
         }
         centre = value;
      }
   }
   public float Rayon
   {
      get { return rayon; }
      private set
      {
         if (value <= 0)
         {
            throw new RayonInvalideException();
         }
         rayon = value;
      }
   }
   public Cercle()
   {
      centre = new Point();
      rayon = RAYON_DÉFAUT;
   }
   public Cercle(Point valCentre, float valRayon)
   {
      Centre = valCentre;
      Rayon = valRayon;
   }
   public Cercle(float x, float y, float valRayon)
   {
      Centre = new Point(x,y);
      Rayon = valRayon;
   }
   public bool Intersecte(Cercle c)
   {
      return Centre.DistanceDe(c.Centre) <= Rayon + c.Rayon;
   }
}
// ...

Combat épique entre le bien et le mal, 1er jet – séance L03

Ce premier contact avec la tension inhérente aux relations entre le bien® et le mal® se voulait aussi illustratif d'un programme dans lequel on trouve des membres de classe, des membres d'instance, un programme simple jouant le rôle de code client, et une forme d'interaction (indirecte) entre deux entités au coeur d'un conflit, soit le héros et le monstre, qui se tapochent dessus allègrement en déterminant les dégâts de chaque taloche grâce en partie à la médiation d'un tiers, la classe GestionnaireDégâts, et de ses services (la méthode de classe Frapper()).

Le code suit.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace z10
{
   class VieInvalide : ApplicationException
   {
   }
   class ForceInvalide : ApplicationException
   {
   }
   class GestionnaireDégâts
   {
      public static int Frapper(int force, int vie)
      {
         return Math.Min(vie, new Random().Next(force));
      }
   }
   class Héros
   {
      int vie;
      int force;
      public int Vie
      {
         get { return vie; }
         set
         {
            if (value < 0)
            {
               throw new VieInvalide();
            }
            vie = value;
         }
      }
      public int Force
      {
         get { return force; }
         private set
         {
            if (value < 0)
            {
               throw new ForceInvalide();
            }
            force = value;
         }
      }
      public Héros(int valVie, int valForce)
      {
         Vie = valVie;
         Force = valForce;
      }
      public bool EstMort()
      {
         return Vie == 0;
      }
   }
   class Monstre
   {
      int vie;
      int agressivité;
      public int Vie
      {
         get { return vie; }
         set
         {
            if (value < 0)
            {
               throw new VieInvalide();
            }
            vie = value;
         }
      }
      public int Agressivité
      {
         get { return agressivité; }
         private set
         {
            if (value < 0)
            {
               throw new ForceInvalide();
            }
            agressivité = value;
         }
      }
      public Monstre(int valVie, int valAgressivité)
      {
         Vie = valVie;
         Agressivité = valAgressivité;
      }
      public bool EstMort()
      {
         return Vie == 0;
      }
   }
   class Program
   {
      static int GénérerVieInitiale()
      {
         return new Random().Next(50) + 50; // 50..99
      }
      static int GénérerForce()
      {
         return new Random().Next(5) + 15; // 15..19
      }
      static int GénérerAgressivité()
      {
         return new Random().Next(10) + 10; // 10..19
      }
      static void Décrire(Monstre m)
      {
         Console.WriteLine("Monstre d'agressivité {0} et de vie {1}", m.Agressivité, m.Vie);
      }
      static void Décrire(Héros h)
      {
         Console.WriteLine("Héros de force {0} et de vie {1}", h.Force, h.Vie);
      }
      static void Main(string[] args)
      {
         Monstre monstre = new Monstre(GénérerVieInitiale(), GénérerAgressivité());
         Héros héros  = new Héros(GénérerVieInitiale(), GénérerForce());
         bool tourHéros = new Random().Next() % 2 == 0;
         while (!monstre.EstMort() && !héros.EstMort())
         {
            Décrire(monstre);
            Décrire(héros);
            if (tourHéros)
            {
               int dégâts = GestionnaireDégâts.Frapper(héros.Force, monstre.Vie);
               Console.WriteLine("Le héros attaque, dégâts: {0}", dégâts);
               monstre.Vie -= dégâts;
            }
            else
            {
               int dégâts = GestionnaireDégâts.Frapper(monstre.Agressivité, héros.Vie);
               Console.WriteLine("Le monstre attaque, dégâts: {0}", dégâts);
               héros.Vie -= dégâts;
            }
            tourHéros = !tourHéros;
         }
         if (monstre.EstMort())
         {
            Console.WriteLine("Le héros a gagné!");
         }
         else
         {
            Console.WriteLine("Le monstre a gagné!");
         }
      }
   }
}

Ébauche d'un tableau dynamique – séance T05

À la séance T05, nous avons entrepris le design d'un tableau dynamique de string. Ce qui suit indique le code que nous avions produit ensemble (au mieux de mon souvenir).

J'ai pris la liberté de faire quelques retouches mineures (par exemple, nous avons déterminé une propriété Length tardivement dans le cours, pour représenter le nombree d'éléments réellement insérés dans le tableau, mais nous ne l'utilisions pas à certains endroits dans le code où cela aurait été pertinent faute d'y avoir pensé auparavant).

class TableauDynamique
{
   // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
   // Choix des attributs
   // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
   
   //
   // - nous avons convenu ensemble de deux attributs, soit un tableau
   //   (qui connaît sa propre capacité) et un nombre d'éléments
   //
   int nbÉlems;
   string [] données;

   // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
   // Prédicats et services simples
   // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
   
   //
   // Capacité: capacité d'entreposage actuelle du tableau dynamique.
   // Pour usage interne seulement
   //
   // NOTE: pourrait être simplifié
   //
   private int Capacité
   {
      get
      {
         int résultat;
         if (données == null)
            résultat = 0;
         else
            résultat = données.Length;
         return résultat;
      }
   }
   
   //
   // Length: nombre d'éléments dans le tableau
   //
   // NOTE: l'attribut nbÉlems pourrait être intégré à cette propriété
   //
   public int Length
   {
      get { return nbÉlems; }
   }
   
   //
   // EstVide(): vrai seulement si le tableau dynamique est vide
   //
   // NOTE: pourrait être une propriété
   //
   public bool EstVide()
   {
      return Length == 0;
   }

   //
   // EstPlein(): vrai seulement si le tableau dynamique est rempli à capacité
   //
   // NOTE: pourrait être une propriété
   // NOTE: pourrait être privé
   //
   public bool EstPlein()
   {
      return Length == Capacité;
   }
   
   // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
   // Constructeurs
   // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

   //
   // Constructeur par défaut
   //
   // - construit un tableau dynamique vide
   // - le choix de créer dynamiquement un tableau de taille nulle
   //   a été fait par les étudiant(e)s. C'est un choix qui se
   //   défend, mais il existe des alternatives comme par exemple
   //   initialiser données à null
   //
   public TableauDynamique()
   {
      nbÉlems = 0;
      données = new string[Length];
   }

   //
   // Constructeur paramétrique
   //
   // - construit un tableau dynamique d'une taille choisie par le code client
   // - le nombre d'éléments demeure nul car on n'a encore rien mis dans le tableau
   //
   // Précondition: la capacité initiale passée en paramètre est valide
   // (supérieure ou égale à zéro)
   //
   // NOTE: tel que mentionné par l'un d'entre vous, il serait sage d'écrire
   //       une méthode de classe EstCapacitéValide() pour localiser la logique
   //       de validation de la capacité d'un tableau dynamique
   //
   public TableauDynamique(int capacitéInitiale)
   {
      if (0 < capacitéInitiale)
         throw new CapacitéInvalideException(); // classe à écrire éventuellement
      nbÉlems = 0;
      données = new string[capacitéInitiale];
   }
   
   // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
   // Consultation d'éléments
   // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

   //
   // GetÉlément()
   //
   // - retourne l'élément à la position indiquée en paramètre
   //
   // Précondition: la position passée en paramètre est valide dans ce tableau,
   // donc dans l'intervalle [0..Length)
   //
   // NOTE: tel que mentionné par l'un d'entre vous, il serait sage d'écrire
   //       une méthode d'instance EstPositionValide() pour localiser la logique
   //       de validation d'une position pour un tableau dynamique donné
   //
   public string GetÉlément(int position)
   {
      if (position < 0 || Length <= position)
         throw new IndexOutOfRangeException(); // par exemple
      return données[position];
   }

   //
   // GetPremierÉlément()
   //
   // - retourne le premier élément du tableau
   //
   // Précondition: le tableau n'est pas vide (assurée par la délégation
   // du traitement à la méthode GetÉlément())
   //
   public string GetPremierÉlément()
   {
      return GetÉlément(0);
   }

   //
   // GetDernierÉlément()
   //
   // - retourne le dernier élément du tableau
   //
   // Précondition: le tableau n'est pas vide (assurée par la délégation
   // du traitement à la méthode GetÉlément())
   //
   public string GetDernierÉlément()
   {
      return GetÉlément(Length - 1);
   }

   // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
   // Modification du contenu du tableau
   // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

   //
   // AjouterFin()
   //
   // - ajoute à la fin du tableau l'élément passé en paramètre
   // - si le tableau est plein, en accroît la capacité (appel à la méthode
   //   Agrandir()) avant l'ajout
   //
   // NOTE: nous ne validons pas que élem != null par souci de simplicité
   //
   public void AjouterFin(string élem)
   {
      if (EstPlein())
         Agrandir();
      données[Length] = élem;
      ++nbÉlem;
   }

   //
   // Agrandir()
   //
   // - accroît la capacité du tableau
   // - n'en modifie pas le nombre d'éléments
   //
   // NOTE: pour usage interne seulement
   //
   // NOTE: la méthode tient compte du fait que la capacité avant appel peut être
   // nulle, ce qui explique l'alternative en début de parcours
   //
   // NOTE: le facteur d'agrandissement proposé en classe était de 1,25 fois la
   // capacité originale, mais cela aurait pu poser problème (par exemple, si la
   // capacité avant appel à Agrandir() est de 1, alors (int)(1 * 1.25) == 1).
   // Nous avons donc choisi un facteur de 2. 
   //
   private void Agrandir()
   {
      const int FACTEUR_CROISSANCE = 2,
                CAPACITÉ_DÉFAUT = 25; // choix du groupe
      int nouvelleCapacité;
      if (Capacité == 0)
         nouvelleCapacité = CAPACITÉ_DÉFAUT;
      else
         nouvelleCapacité = Capacité * FACTEUR_CROISSANCE;
      string [] tab = new string[nouvelleCapacité];
      for(int i = 0; i < Length; ++i)
         tab[i] = données[i];
      donneés = tab;
   }
   
   //
   // IL RESTE ENCORE BEAUCOUP À FAIRE (VOIR LES CONSIGNES)
   //
}

Formes – séance L06

Le programme que nous avons rédigé pour mettre en relief le rôle de l'héritage, du polymorphisme et de l'abstraction suit.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FormesPolymorphisme
{
   //
   //
   //
   abstract class Forme
   {
      public char Symbole
      {
         get; private set;
      }
      public Forme(char symbole)
      {
         Symbole = symbole;
      }
      public abstract void Dessiner();
      //{
      //   // hum...
      //   Console.WriteLine("J'sais pas quoi faire icitte, man!");
      //}
   }
   //
   //
   //
   class Carré : Forme
   {
      const int HAUTEUR_MINIMUM = 1;
      int hauteur;
      public int Hauteur
      {
         get { return hauteur; }
         private set
         {
            if (value < HAUTEUR_MINIMUM)
               throw new ArgumentOutOfRangeException();
            hauteur = value;
         }
      }
      public Carré(int hau, char symbole)
         : base(symbole)
      {
         Hauteur = hau;
      }
      public override void Dessiner()
      {
         for (int i = 0; i < Hauteur; ++i)
         {
            for (int j = 0; j < Hauteur; ++j)
            {
               Console.Write(Symbole);
            }
            Console.WriteLine();
         }
      }
   }
   //
   //
   //
   class Rectangle : Forme
   {
      const int HAUTEUR_MINIMUM = 1;
      const int LARGEUR_MINIMUM = 1;
      int hauteur;
      int largeur;
      public int Hauteur
      {
         get { return hauteur; }
         private set
         {
            if (value < HAUTEUR_MINIMUM)
               throw new ArgumentOutOfRangeException();
            hauteur = value;
         }
      }
      public int Largeur
      {
         get { return largeur; }
         private set
         {
            if (value < LARGEUR_MINIMUM)
               throw new ArgumentOutOfRangeException();
            largeur = value;
         }
      }
      public Rectangle(int hau, int lar, char symbole)
         : base(symbole)
      {
         Hauteur = hau;
         Largeur = lar;
      }
      public override void Dessiner()
      {
         for (int i = 0; i < Hauteur; ++i)
         {
            for (int j = 0; j < Largeur; ++j)
            {
               Console.Write(Symbole);
            }
            Console.WriteLine();
         }
      }
   }
   //
   //
   //
   class Triangle : Forme
   {
      const int HAUTEUR_MINIMUM = 1;
      int hauteur;
      public int Hauteur
      {
         get { return hauteur; }
         private set
         {
            if (value < HAUTEUR_MINIMUM)
               throw new ArgumentOutOfRangeException();
            hauteur = value;
         }
      }
      public Triangle(int hau, char symbole)
         : base(symbole)
      {
         Hauteur = hau;
      }
      public override void Dessiner()
      {
         for (int i = 1; i <= Hauteur; ++i)
         {
            int nbBlancs = Hauteur - i;
            int nbSymboles = 2 * i - 1;
            for (int j = 1; j <= nbBlancs; ++j)
            {
               Console.Write(' ');
            }
            for (int j = 1; j <= nbSymboles; ++j)
            {
               Console.Write(Symbole);
            }
            Console.WriteLine();
         }
      }
   }
   //
   //
   //
   class Program
   {
      static void Main(string[] args)
      {
         Forme[] zeFormes = new Forme[3];
         zeFormes[0] = new Carré(3, '#');
         zeFormes[1] = new Rectangle(3, 5, 'Z');
         zeFormes[2] = new Triangle(7, 'W');
         for (int i = 0; i < zeFormes.Length; ++i)
            zeFormes[i].Dessiner();
         //
         // Ce qui suit doit être interdit, genre!
         //
         //Forme f = new Forme('3');
         //f.Dessiner();
      }
   }
}

Résultats des travaux pratiques

Ce qui suit collige les résultats des divers travaux pratiques réalisés au cours de la session. La moyenne est exprimée en pourcentage, peu importe l'échelle d'origine pour la correction des travaux en question. Les absent(e)s sont considéré(e)s avoir eu zéro. La valeur de exprimée dans le tableau reflète toutefois le nombre de participant(e)s au travail pratique; cette valeur devrait normalement être équivalente au nombre d'étudiant(e)s inscrit(e)s au cours.

Travail Remise

Laboratoire 1 (une archive de base pour le travail est disponible ici)

L03

Laboratoire 2 (une archive de base pour le travail est disponible ici)

T04

Laboratoire 3 (l'archive de base pour vous aider à démarrer est disponible ici; le programme de test qui s'y trouve est incomplet, et c'est à vous de l'enrichir pour qu'il devienne pertinent)

T06

Laboratoire 4

L10

Laboratoire 5 (le code source du programme principal imposé est disponible ici)

L12

Activité synthèse – (le code source du programme principal imposé est disponible ici)

L??

Résultats des minitests et des examens

Les résultats des minitests et des examens suivent.

Contrôle Séance

Minitest #1

L05

Examen intra

T07

Minitest #2

T12

Examen final

L14

Correction

Je corrige les programmes en appliquant des codes de correction. Vous trouverez ici la liste des codes les plus fréquents.
Ma stratégie de correction en tant que telle (pour le code, à tout le moins) est résumée ici.

Section commune à toutes les classes de 420-202

Bien que cette page soit principalement destinée aux étudiant(e)s du groupe de Patrice (par exemple, les résultats aux travaux pratiques sont ceux de mes étudiant(e)s seulement), ce qui suit concerne à les étudiant(e)s de tous les groupes de 420-202.

Énoncés des laboratoires

Les énoncés des laboratoires à faire cette session apparaîtront dans la table ci-dessous au fur et à mesure qu'ils vous auront été distribués.

Énoncé / archive de base Date de remise

Laboratoire 1

L03

Laboratoire 2

T04

Laboratoire 3

T06

Laboratoire 4

L10

Laboratoire 5

L12

Pierre a eu la gentillesse de produire un document listant les tests qu'il a effectués sur les exemplaires du laboratoire 3 remis dans sa classe. Si vous souhaitez connaître sa stratégie de tests, voir ceci.

Dates à retenir

Outre les dates de remise des laboratoires, il existe un petit nombre de dates importantes au cours de la session. En particulier, celles répertoriées dans la table ci-dessous.

Quoi? Quand?

Minitest #1

L05

Examen intra

T07

Minitest #2

T12

Examen final

L14


Valid XHTML 1.0 Transitional

CSS Valide !