Aller au menu - Aller au contenu

Icône TP Programmation Orientée Objet

Mise à jour : 02/02/2012
Difficulté : Intermédiaire Intermédiaire Creative Commons BY-NC-SA
22 955 visites depuis 7 jours, dont 298 sur ce chapitre classé 15/786
Dans ce TP, nous allons essayer de mettre en pratique ce que nous avons appris en programmation orientée objet avec le C#.

Difficile d’avoir un exercice qui nécessite toutes les notions que nous avons apprises. Aussi, certaines sont laissées de côté.
Avec ce TP, vous aurez l'occasion de vous entraîner à créer des classes, à manipuler l’héritage et vous pourrez vous confronter à des situations un peu différentes de la théorie !

Le but du TP est de créer une mini application de gestion bancaire où nous pourrons gérer des comptes qui peuvent faire des opérations bancaires entre eux. Ne vous attendez pas non plus à refaire les applications de la banque de France, on est là pour s'entrainer. ;) Ce TP sera décomposé en deux parties.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Instructions pour réaliser le TP

Alors voici l'énoncé de la première partie du TP.

Dans notre mini application bancaire tous les montants utilisés seront des décimaux.
Nous allons devoir gérer des comptes bancaires. Un compte est composé :

  • D’un Solde (calculé, mais non modifiable) ;
  • D’un Propriétaire (nom du propriétaire du compte) ;
  • D’une liste d’opérations interne permettant de garder l’historique du compte, non accessible par les autres objets ;
  • D’une méthode permettant de Crediter() le compte, prenant une somme en paramètre ;
  • D’une méthode permettant de Crediter() le compte, prenant une somme et un compte en paramètres, créditant le compte et débitant le compte passé en paramètres ;
  • D’une méthode permettant de Debiter() le compte, prenant une somme en paramètre ;
  • D’une méthode permettant de Débiter() le compte, prenant une somme et un compte bancaire en paramètres, débitant le compte et créditant le compte passé en paramètres ;
  • D’une méthode qui permet d’afficher le résumé d’un compte.


Un compte courant est une sorte de compte et est composé :

  • De tout ce qui compose un compte ;
  • D’un découvert autorisé, non modifiable, défini à l’ouverture du compte ;
  • Le résumé d’un compte courant affiche le solde, le propriétaire, le découvert autorisé ainsi que les opérations sur le compte.


Un compte épargne entreprise est une sorte de compte et est composé :

  • De tout ce qui compose un compte ;
  • D’un taux d’abondement, défini à l’ouverture du compte en fonction de l’ancienneté du salarié. Un taux est un double compris entre 0 et 1. (5% = 0.05) ;

Le solde doit tenir compte du taux d’abondement, mais l’abondement n’est pas calculé lors des transactions car l’entreprise verse l’abondement quand elle le souhaite ; le résumé d’un compte épargne entreprise affiche le solde, le propriétaire, le taux d’abondement ainsi que les opérations sur le compte.


Une opération bancaire est composée :

  • D’un montant ;
  • D’un type de mouvement, crédit ou débit.


Créer une telle application avec les informations suivantes :

  • Le compte courant de Nicolas a un découvert autorisé de 2000€
  • Le compte épargne entreprise de Nicolas a un taux de 2%
  • Le compte courant de Jérémie a un découvert autorisé de 500€
  • Nicolas touche son salaire de 100€, pas cher payé !
  • Il fait le plein de sa voiture : 50€
  • Il met de côté sur son compte épargne entreprise la coquette somme de 20€
  • Puis il reçoit un cadeau de la banque de 100€ car il a ouvert son compte pendant la période promotionnelle
  • Il remet ses 20€ sur son compte bancaire car finalement, il ne se sent pas méga en sécurité
  • Jérémie achète un nouveau PC : 500€
  • Puis il rembourse ses dettes à Nicolas : 200€


L’application doit indiquer les soldes de chacun de ces comptes :

Code : Console
Solde compte courant de Nicolas : 250
Solde compte épargne de Nicolas : 102,00
Solde compte courant de Jérémie : -700


Ensuite, nous afficherons le résumé du compte courant de Nicolas et de son compte épargne entreprise. Ce qui nous donnera :

Code : Console
Résumé du compte de Nicolas
*******************************************
Compte courant de Nicolas
        Solde : 250
        Découvert autorisé : 2000


Opérations :
        +100
        -50
        -20
        +20
        +200
*******************************************


Résumé du compte épargne de Nicolas
########################################################
Compte épargne entreprise de Nicolas
        Solde : 102,00
        Taux : 0,02


Opérations :
        +20
        +100
        -20
########################################################


Bon, j’ai bien expliqué, ce n’est pas si compliqué, sauf si bien sûr vous n’avez pas lu ou pas compris les chapitres précédents. Dans ce cas, n’hésitez pas à y rejeter un coup d’œil.
Sinon, il suffit de bien décomposer tout en créant les classes les unes après les autres.

Bon courage ! :)

Correction

Ne regardez pas tout de suite la correction. Faites le TP. Il est important de manipuler les classes. Vous allez faire ça très régulièrement. Cela doit devenir un réflexe et pour que cela le devienne, il faut pratiquer !

Toujours est-il que voici ma correction. Peut-être que le plus dur est de modéliser correctement l’application ?
Il y a plusieurs solutions bien sûr pour créer ce petit programme, voici celle que je propose.

Tout d’abord, nous devons manipuler des comptes courant et des comptes épargne entreprise, qui sont des sortes de comptes.
On en déduit qu’un compte courant hérite d’un compte. De même, un compte épargne entreprise hérite également d’un compte. D’ailleurs, un compte a-t-il vraiment une raison d’être à part entière ? Nous ne créons jamais de compte « généraliste », seulement des comptes spécialisés. Nous allons donc créer la classe compte en tant que classe abstraite.

Ce qui nous donnera les classes suivantes :


Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public abstract class Compte
{
}

public class CompteCourant : Compte
{
}

public class CompteEpargneEntreprise : Compte
{
}


Nous avons également dit qu’un compte était composé d’une liste d’opérations qui possèdent un montant et un type de mouvement. Le type de mouvement pouvant prendre 2 valeurs, il parait logique d’utiliser une énumération :

Code : C#
1
2
3
4
5
public enum Mouvement
{
    Credit,
    Debit
}


La classe d’opération étant :

Code : C#
1
2
3
4
5
public class Operation
{
    public Mouvement TypeDeMouvement { get; set; }
    public decimal Montant { get; set; }
}


Voilà pour la base.

Ensuite, la classe Compte doit posséder un nom de propriétaire et un solde qui peut être redéfini dans la classe CompteEpargneEntreprise. Ce solde est calculé à partir d’une liste d’opérations. Le solde est donc une propriété en lecture seule, virtuelle car nécessitant d’être redéfinie :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class Compte
{
    protected List<Operation> listeOperations;
    public string Proprietaire { get; set; }

    public virtual decimal Solde
    {
        get
        {
            decimal total = 0;
            foreach (Operation operation in listeOperations)
            {
                if (operation.TypeDeMouvement == Mouvement.Credit)
                    total += operation.Montant;
                else
                    total -= operation.Montant;
            }
            return total;
        }
    }
}


La liste des opérations est une variable membre, déclarée en protected car nous allons en avoir besoin dans la classe CompteCourant qui en hérite, afin d’afficher un résumé des opérations.

La propriété Solde n’est pas très compliquée en soit, il suffit de parcourir la liste des opérations et en fonction du type de mouvement, ajouter ou retrancher le montant. Comme c’est une propriété en lecture seule, seul le get est défini.

N’oubliez pas que la liste doit être initialisée avant d’être utilisée, sinon nous aurons une erreur. Le constructeur est un endroit approprié pour le faire :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public abstract class Compte
{
    // [...]
    // [Code précédent enlevé pour plus de lisibilité]
    // [...]

    public Compte()
    {
        listeOperations = new List<Operation>();
    }
}


La classe doit ensuite posséder des méthodes permettant de créditer ou de débiter le compte, ce qui donne :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract class Compte
{
    // [...]
    // [Code précédent enlevé pour plus de lisibilité]
    // [...]

    public void Crediter(decimal montant)
    {
        Operation operation = new Operation { Montant = montant, TypeDeMouvement = Mouvement.Credit};
        listeOperations.Add(operation);
    }

    public void Crediter(decimal montant, Compte compte)
    {
        Crediter(montant);
        compte.Debiter(montant);
    }

    public void Debiter(decimal montant)
    {
        Operation operation = new Operation { Montant = montant, TypeDeMouvement = Mouvement.Debit };
        listeOperations.Add(operation);
    }

    public void Debiter(decimal montant, Compte compte)
    {
        Debiter(montant);
        compte.Crediter(montant);
    }
}


Le principe est de créer une opération et de l’ajouter à la liste des opérations. L’astuce ici consiste à réutiliser les méthodes de la classe pour écrire les autres formes des méthodes, ce qui simplifie le travail et facilitera les éventuelles futures opérations de maintenance.

Enfin, chaque classe dérivée de la classe Compte doit afficher un résumé du compte. Nous pouvons donc forcer ces classes à devoir implémenter cette méthode en utilisant une méthode abstraite :

Code : C#
1
2
3
4
5
6
7
8
public abstract class Compte
{
    // [...]
    // [Code précédent enlevé pour plus de lisibilité]
    // [...]

    public abstract void AfficherResume();
}


Voilà pour la classe Compte. En toute logique, c’est elle qui contient le plus de méthodes afin de factoriser le maximum de code commun dans la classe mère.

Passons à la classe CompteEpargneEntreprise, qui hérite de la classe Compte. Elle possède un taux d’abondement qui est défini à la création du compte. Il est donc ici intéressant de forcer le positionnement de ce taux lors de la création de la classe, c'est-à-dire en utilisant un constructeur avec un paramètre :

Code : C#
1
2
3
4
5
6
7
8
9
public class CompteEpargneEntreprise : Compte
{
    private double tauxAbondement;

    public CompteEpargneEntreprise(double taux)
    {
        tauxAbondement = taux;
    }
}


Ce taux est utilisé pour calculer le solde du compte en faisant la somme de toutes les opérations et en appliquant le taux. Ce qui revient à appeler le calcul du solde de la classe mère et à lui appliquer le taux. Nous substituons donc la propriété Solde et utilisons le calcul fait dans la classe Compte:

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class CompteEpargneEntreprise : Compte
{
    // [...]
    // [Code précédent enlevé pour plus de lisibilité]
    // [...]

    public override decimal Solde
    {
        get
        {
            return base.Solde * (decimal)(1 +  tauxAbondement);
        }
    }
}


Rien de plus simple, en utilisant le mot-clé base pour appeler le solde de la classe mère. Vous noterez également que nous avons eu besoin de caster le taux qui est un double afin de pouvoir le multiplier à un décimal.

Enfin, cette classe se doit de fournir une implémentation de la méthode AfficherResume() :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CompteEpargneEntreprise : Compte
{
    // [...]
    // [Code précédent enlevé pour plus de lisibilité]
    // [...]

    public override void AfficherResume()
    {
        Console.WriteLine("########################################################");
        Console.WriteLine("Compte épargne entreprise de " + Proprietaire);
        Console.WriteLine("\tSolde : " + Solde);
        Console.WriteLine("\tTaux : " + tauxAbondement);
        Console.WriteLine("\n\nOpérations :");
        foreach (Operation operation in listeOperations)
        {
            if (operation.TypeDeMouvement == Mouvement.Credit)
                Console.Write("\t+");
            else
                Console.Write("\t-");
            Console.WriteLine(operation.Montant);
        }
        Console.WriteLine("########################################################");
    }
}


Il s’agit d’une banale méthode d’affichage où nous parcourons la liste contenant les opérations et affichons le montant en fonction du type de mouvement.
Voilà pour la classe CompteEpargneEntreprise.

Il ne reste plus que la classe CompteCourant qui doit également dériver de Compte :

Code : C#
1
2
3
public class CompteCourant : Compte
{
}


Un compte courant doit posséder un découvert autorisé, défini à la création du compte courant. Nous utilisons comme avant un constructeur avec un paramètre :

Code : C#
1
2
3
4
5
6
7
8
9
public class CompteCourant : Compte
{
    private decimal decouvertAutorise;

    public CompteCourant(decimal decouvert)
    {
        decouvertAutorise = decouvert;
    }
}


Il ne restera plus qu’à fournir une implémentation de la méthode d’affichage du résumé :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CompteCourant : Compte
{
    // [...]
    // [Code précédent enlevé pour plus de lisibilité]
    // [...]

    public override void AfficherResume()
    {
        Console.WriteLine("*******************************************");
        Console.WriteLine("Compte courant de " + Proprietaire);
        Console.WriteLine("\tSolde : " + Solde);
        Console.WriteLine("\tDécouvert autorisé : " + decouvertAutorise);
        Console.WriteLine("\n\nOpérations :");
        foreach (Operation operation in listeOperations)
        {
            if (operation.TypeDeMouvement == Mouvement.Credit)
                Console.Write("\t+");
            else
                Console.Write("\t-");
            Console.WriteLine(operation.Montant);
        }
        Console.WriteLine("*******************************************");
    }
}


Voilà pour nos objets. Cela en fait un petit paquet. Mais ce n’est pas fini, il faut maintenant créer des comptes et faire des opérations.

L’énoncé consiste à faire les instanciations suivantes, depuis notre méthode Main() :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
CompteCourant compteNicolas = new CompteCourant(2000) { Proprietaire = "Nicolas" };
CompteEpargneEntreprise compteEpargneNicolas = new CompteEpargneEntreprise(0.02) { Proprietaire = "Nicolas" };
CompteCourant compteJeremie = new CompteCourant(500) { Proprietaire = "Jérémie" };

compteNicolas.Crediter(100);
compteNicolas.Debiter(50);

compteEpargneNicolas.Crediter(20, compteNicolas);
compteEpargneNicolas.Crediter(100);

compteEpargneNicolas.Debiter(20, compteNicolas);

compteJeremie.Debiter(500);
compteJeremie.Debiter(200, compteNicolas);

Console.WriteLine("Solde compte courant de " + compteNicolas.Proprietaire + " : " + compteNicolas.Solde);
Console.WriteLine("Solde compte épargne de " + compteEpargneNicolas.Proprietaire + " : " + compteEpargneNicolas.Solde);
Console.WriteLine("Solde compte courant de " + compteJeremie.Proprietaire + " : " + compteJeremie.Solde);
Console.WriteLine("\n");


Rien d’extraordinaire.

Il ne reste plus qu’à afficher le résumé des deux comptes demandés :

Code : C#
1
2
3
4
5
6
7
Console.WriteLine("Résumé du compte de Nicolas");
compteNicolas.AfficherResume();
Console.WriteLine("\n");

Console.WriteLine("Résumé du compte épargne de Nicolas");
compteEpargneNicolas.AfficherResume();
Console.WriteLine("\n");


Nous en avons fini avec la première partie du TP !
Si vous avez bien compris comment construire des classes, comment les faire hériter entre elles et les interfaces, ce TP n’a pas dû vous poser trop de problèmes. :)

Aller plus loin

Il y a plusieurs choses que je n’aime pas dans ce code. La première est la répétition. Dans les deux méthodes AfficherResume() des classes CompteCourant et CompteEpargneEntreprise on affiche les opérations de la même façon. Si jamais je dois modifier ce code (bug ou évolution), je devrais le faire dans les deux classes, au risque d’en oublier une. Il faut donc factoriser ce code. Il existe pour cela plusieurs solutions simples.

La première est d’utiliser une méthode statique pour afficher la liste des opérations. Cette méthode pourrait être située dans une classe utilitaire qui prend en paramètre la liste des opérations :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public static class Helper
{
    public static void AfficheOperations(List<Operation> operations)
    {
        Console.WriteLine("\n\nOpérations :");
        foreach (Operation operation in operations)
        {
            if (operation.TypeDeMouvement == Mouvement.Credit)
                Console.Write("\t+");
            else
                Console.Write("\t-");
            Console.WriteLine(operation.Montant);
        }
    }
}


Il ne reste plus qu’à utiliser cette méthode statique depuis nos méthodes AfficherResume(), par exemple pour le compte épargne entreprise :

Code : C#
1
2
3
4
5
6
7
8
9
public override void AfficherResume()
{
    Console.WriteLine("########################################################");
    Console.WriteLine("Compte épargne entreprise de " + Proprietaire);
    Console.WriteLine("\tSolde : " + Solde);
    Console.WriteLine("\tTaux : " + tauxAbondement);
    Helper.AfficheOperations(listeOperations);
    Console.WriteLine("########################################################");
}


La deuxième solution est de créer la méthode AfficheOperations() dans la classe abstraite Compte, ce qui permet de s’affranchir de passer la liste en paramètres vu que la classe Compte la connait déjà :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
protected void AfficheOperations()
{
    Console.WriteLine("\n\nOpérations :");
    foreach (Operation operation in listeOperations)
    {
        if (operation.TypeDeMouvement == Mouvement.Credit)
            Console.Write("\t+");
        else
            Console.Write("\t-");
        Console.WriteLine(operation.Montant);
    }
}


La méthode a tout intérêt à être déclarée en protected, afin qu’elle puisse servir aux classes filles mais pas à l’extérieur. Elle s’utilisera de cette façon, par exemple dans la classe CompteEpargneEntreprise :

Code : C#
1
2
3
4
5
6
7
8
9
public override void AfficherResume()
{
    Console.WriteLine("########################################################");
    Console.WriteLine("Compte épargne entreprise de " + Proprietaire);
    Console.WriteLine("\tSolde : " + Solde);
    Console.WriteLine("\tTaux : " + tauxAbondement);
    AfficheOperations();
    Console.WriteLine("########################################################");
}


Dès que l’on peut factoriser du code, il ne faut pas hésiter. Si nous avons demain besoin de créer un nouveau type de compte, nous serons ravis de pouvoir nous servir de méthodes toutes prêtes nous simplifiant le travail.

Il pourrait être intéressant également d'encapsuler l'enregistrement d'une opération dans une méthode. Ce qui permet de moins se répéter (même si ici, le code est vraiment petit) mais qui permet surtout de séparer la logique d'enregistrement d'une opération afin que cela soit plus facile ultérieurement à modifier, maintenir ou complexifier. Par exemple, via une méthode :

Code : C#
1
2
3
4
5
private void EnregistrerOperation(Mouvement typeDeMouvement, decimal montant) 
{
    Operation operation = new Operation { Montant = montant, TypeDeMouvement = typeDeMouvement};
    listeOperations.Add(operation);
}


Cela permet également de faire en sorte que le type de mouvement soit un paramètre de la méthode.

Deuxième partie du TP

Nous voici maintenant dans la deuxième partie du TP. La banque souhaite proposer un nouveau type de compte, le livret ToutBénef. Dans cette banque, le livret ToutBénéf fonctionne comme le compte épargne entreprise. C’est-à-dire qu’il accepte un taux en paramètres et applique ce taux au moment de la restitution du solde.

La première idée qui vient à l’esprit est créer une classe LivretToutBenef qui hérite de CompteEpargneEntreprise. Mais ceci pose un problème si jamais le compte épargne entreprise doit évoluer, et c’est justement ce que le directeur de la banque vient de me confier. Donc, il vous interdit à juste titre d’hériter de ses fonctionnalités.

Ce que vous allez donc faire ici, c’est de considérer que le fait qu’un compte puisse faire des bénéfices soit en fait un comportement qui est fourni au moment où on instancie un compte. Il existe plusieurs comportements dont on doit fournir les implémentations :

  • Le comportement de bénéfice à taux fixe
  • Le comportement de bénéfice aléatoire
  • Le comportement où il n’y a aucun bénéfice


Chaque comportement est une classe qui respecte le contrat suivant :

Code : C#
1
2
3
4
5
public interface ICalculateurDeBenefice
{
    decimal CalculeBenefice(decimal solde);
    double Taux { get; }
}


Écrivez donc ces trois classes de comportement ainsi que le livret ToutBénéf qui possède un taux fixe de 2.75% et qui a été crédité une première fois de 800€ et une seconde fois de 200€. Affichez enfin son résumé qui devra tenir compte du taux du calculateur de bénéfice.
Réécrivez ensuite la classe CompteCourant de manière à ce qu’elle ait un comportement où il n’y a pas de bénéfice.
Enfin, la classe CompteEpargneEntreprise subira une évolution pour fonctionner avec un comportement de bénéfice aléatoire (tiré entre 0 et 1).


C’est parti.

Correction

Ici c’est un peu plus compliqué. Vous n’êtes sans doute pas complètement familiers avec les notions d’interfaces, aussi avant de vous livrer la correction, je vais vous donner quelques pistes.
Le fait d’avoir un comportement est finalement très simple. Il suffit d’avoir un membre privé dans la classe LivretToutBenef du type de l’interface. Ce membre privé sera affecté à la valeur passée en paramètre du constructeur. C’est-à-dire :

Code : C#
1
2
3
4
5
6
7
8
9
public class LivretToutBenef : Compte
{
    private ICalculateurDeBenefice calculateurDeBenefice;

    public LivretToutBenef(ICalculateurDeBenefice calculateur)
    {
        calculateurDeBenefice = calculateur;
    }
}


Désormais, les opérations devront se faire avec cette variable, calculateurDeBenefice. On aura également besoin d’instancier au préalable le comportement voulu et de le passer en paramètres du constructeur. Retournez donc tenter de réaliser ce TP avant de voir la suite de la correction ;) .

C’est fait ? Alors voici ma correction.
Nous avions donc cette interface imposée :

Code : C#
1
2
3
4
5
public interface ICalculateurDeBenefice
{
    decimal CalculeBenefice(decimal solde);
    double Taux { get; }
}


Nous avons besoin d’écrire plusieurs classes qui implémentent cette interface. La première est la classe de bénéfice à taux fixe :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class BeneficeATauxFixe : ICalculateurDeBenefice
{
    private double taux;

    public BeneficeATauxFixe(double tauxFixe)
    {
        taux = tauxFixe;
    }

    public decimal CalculeBenefice(decimal solde)
    {
        return solde * (decimal)(1 + taux);
    }

    public double Taux 
    { 
        get 
        { 
            return taux; 
        } 
    }
}


Nous avons dit qu’elle devait prendre un taux en paramètres, le constructeur est l’endroit indiqué pour cela. Ensuite, la méthode de calcul est très simple, il suffit d’appliquer la formule au solde. Enfin, la propriété retourne le taux.

La classe suivante est la classe de bénéfice aléatoire. Là, pas besoin de paramètres, il suffit de tirer le nombre aléatoire dans le constructeur grâce à la méthode NextDouble(), ce qui donne :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BeneficeAleatoire : ICalculateurDeBenefice
{
    private double taux;
    private Random random;

    public BeneficeAleatoire()
    {
        random = new Random();
        taux = random.NextDouble();
    }

    public decimal CalculeBenefice(decimal solde)
    {
        return solde * (decimal)(1 + taux);
    }

    public double Taux 
    { 
        get 
        { 
            return taux; 
        } 
    }
}


Enfin, il faudra créer la classe avec aucun bénéfice :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class AucunBenefice : ICalculateurDeBenefice
{
    public decimal CalculeBenefice(decimal solde)
    {
        return solde;
    }

    public double Taux 
    { 
        get 
        { 
            return 0; 
        } 
    }
}


Rien de sorcier !
Là où cela se complique un peu, c’est pour la classe LivretToutBenef. Elle doit bien sûr dériver de la classe de base Compte et posséder un membre privé de type ICalculateurDeBenefice :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class LivretToutBenef : Compte
{
    private ICalculateurDeBenefice calculateurDeBenefice;

    public LivretToutBenef(ICalculateurDeBenefice calculateur)
    {
        calculateurDeBenefice = calculateur;
    }

    public override decimal Solde
    {
        get 
        {
            return calculateurDeBenefice.CalculeBenefice(base.Solde); 
        }
    }

    public override void AfficherResume()
    {
        Console.WriteLine("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
        Console.WriteLine("Livret ToutBénéf de " + Proprietaire);
        Console.WriteLine("\tSolde : " + Solde);
        Console.WriteLine("\tTaux : " + calculateurDeBenefice.Taux);
        AfficheOperations();
        Console.WriteLine("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
    }
}


Dans le constructeur, la variable est initialisée avec un objet de type ICalculateurDeBenefice. Ensuite, pour calculer le solde, il suffit d’appeler la méthode CalculeBenefice avec le solde de base en paramètres. De même, pour faire apparaître le taux dans le résumé, on pourra utiliser la propriété Taux de l’interface.

Il ne reste qu’à souscrire à un Livret ToutBénéf en lui passant un objet de bénéfice à taux fixe en paramètre du constructeur, et de faire les opérations bancaires demandées. Ce qui donne :

Code : C#
1
2
3
4
5
6
7
8
ICalculateurDeBenefice beneficeATauxFixe = new BeneficeATauxFixe(0.275);
LivretToutBenef livretToutBenefNicolas = new LivretToutBenef(beneficeATauxFixe);
livretToutBenefNicolas.Crediter(800);
livretToutBenefNicolas.Crediter(200);

Console.WriteLine("Résumé du livret Toutbénef");
livretToutBenefNicolas.AfficherResume();
Console.WriteLine("\n");


Ce qui donne :

Code : Console
Résumé du livret Toutbénef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Livret ToutBénéf de
        Solde : 1275,000
        Taux : 0,275


Opérations :
        +800
        +200
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


Maintenant, nous devons adapter la classe CompteEpargneEntreprise pour qu’elle puisse fonctionner avec un comportement. Cela devient tout simple et se rapproche beaucoup du livret ToutBénéf :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class CompteEpargneEntreprise : Compte
{
    private ICalculateurDeBenefice calculateurDeBenefice;

    public CompteEpargneEntreprise(ICalculateurDeBenefice calculateur)
    {
        calculateurDeBenefice = calculateur;
    }

    public override decimal Solde
    {
        get 
        {
            return calculateurDeBenefice.CalculeBenefice(base.Solde);
        }
    }

    public override void AfficherResume()
    {
        Console.WriteLine("########################################################");
        Console.WriteLine("Compte épargne entreprise de " + Proprietaire);
        Console.WriteLine("\tSolde : " + Solde);
        Console.WriteLine("\tTaux : " + calculateurDeBenefice.Taux);
        AfficheOperations();
        Console.WriteLine("########################################################");
    }
}


Il faut maintenant changer l’instanciation de l’objet CompteEpargneEntreprise en lui passant en paramètres un objet de type bénéfice aléatoire :

Code : C#
1
2
ICalculateurDeBenefice beneficeAleatoire = new BeneficeAleatoire();
CompteEpargneEntreprise compteEpargneNicolas = new CompteEpargneEntreprise(beneficeAleatoire) { Proprietaire = "Nicolas" };


Reste le compte courant qui suit le même principe :

Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class CompteCourant : Compte
{
    private decimal decouvertAutorise;
    private ICalculateurDeBenefice calculateurDeBenefice;

    public CompteCourant(decimal decouvert, ICalculateurDeBenefice calculateur)
    {
        decouvertAutorise = decouvert;
        calculateurDeBenefice = calculateur;
    }

    public override decimal Solde
    {
        get 
        {
            return calculateurDeBenefice.CalculeBenefice(base.Solde);
        }
    }

    public override void AfficherResume()
    {
        Console.WriteLine("*******************************************");
        Console.WriteLine("Compte courant de " + Proprietaire);
        Console.WriteLine("\tSolde : " + Solde);
        Console.WriteLine("\tDécouvert autorisé : " + decouvertAutorise);
        Console.WriteLine("\tTaux : " + calculateurDeBenefice.Taux);
        AfficheOperations();
        Console.WriteLine("*******************************************");
    }
}


Que l’on pourra instancier avec un objet de type aucun bénéfice :

Code : C#
1
2
ICalculateurDeBenefice aucunBenefice = new AucunBenefice();
CompteCourant compteNicolas = new CompteCourant(2000, aucunBenefice) { Proprietaire = "Nicolas" };


Et voilà.
L’avantage ici est d’avoir séparé les responsabilités dans différentes classes. Si jamais nous créons un nouveau compte qui est rémunéré grâce à un bénéfice à taux fixe, il suffira de réutiliser ce comportement et le tour est joué.

À noter que les trois calculs de la propriété Solde sont identiques, il pourrait être judicieux de le factoriser dans la classe mère Compte. Ceci implique que la classe mère possède elle-même le membre protégé du type de l’interface.

Voilà pour ce TP. J’espère que vous aurez réussi avec brio toutes les créations de classes et que vous ne vous êtes pas mélangés dans les mots-clés.
Vous verrez que vous aurez très souvent besoin d’écrire des classes dans ce genre afin de créer une application. C’est un élément indispensable du C# qu’il est primordial de maîtriser.

N’hésitez pas à faire des variations sur ce TP ou à créer d’autres petits programmes simples vous permettant de vous entrainer.
Voilà pour ce TP. J’espère que vous aurez réussi avec brio toutes les créations de classes et que vous ne vous êtes pas mélangés dans les mots-clés.
Vous verrez que vous aurez très souvent besoin d’écrire des classes de ce genre afin de créer une application. C’est un élément indispensable du C# qu’il est primordial de maîtriser.

N’hésitez pas à faire des variations sur ce TP ou à créer d’autres petits programmes simples vous permettant de vous entrainer.
Chapitre précédent Sommaire Chapitre suivant

Partager

9 commentaires pour "TP Programmation Orientée Objet"
Note moyenne : 3.05 / 4 (230 votes)
Pseudo Commentaire
Hors ligne eldrole # Posté le 20/03/2012 à 17:42:13

Heu c'est juste une question toute bête mais dans la première partie du TP, il est écrit que Jérémie a droit à un découvert de 500€ et il finit avec un découvert de 700€?
Hors ligne Mandi # Posté le 21/03/2012 à 14:46:08
C'est en forgeant...
Avatar

Avis : Bon

Bonjour, et merci pour vos réponses ! :)

Citation : Mandi

Tout d'abord : c'est ma classe de plus haut niveau (Compte) qui porte le "ICalculateurDeBenefice". Dans un soucis de factorisation, cela me semblait logique.
Y avait-il une contre-indication dans l'énoncé ? Est-ce une bonne ou une mauvaise pratique ?


Citation : nico.pyright
Pour le reste, je ne vous empêcherai pas faire en sorte que ça soit la classe Compte qui implémente l'interface.

En fait, la variable membre ICalculateurDeBenefice est portée par la classe Compte et non par chaque classe fille, les seules classes à implémenter l'interface sont évidemment celles susceptibles de générer du bénéfice ^^ :

Code : C#
1
2
3
4
5
6
7
8
9
abstract class Compte
{
	/// <summary>
	/// Permet de calculer les bénéfices du compte.
	/// </summary>
	protected ICalculateurDeBenefice calculateurDeBenefice;

	// ... reste du code
}


Je voulais savoir si cela était logique en terme de factorisation.

Encore merci pour ce big tuto. J'ai terminé le TP sur les Enumerateurs, c'est très instructif. Pas évident, mais très instructif ! :pirate:

SdZ Addict!
Non au langage SMS!!
 
Hors ligne Darioo2 # Posté le 30/03/2012 à 13:47:34
Avatar

Avis : Bon

Bonjour,
Voici la première partie du TP que je viens de terminer:

Méthode main():
Code : C#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
static void Main(string[] args)
        {
            Compte[] comptes = new Compte[3];
            comptes[0] = new CompteCourant(0, "Nicolas", 2000);
            comptes[1] = new CompteEpargne(0, "Nicolas", 0.02);
            comptes[2] = new CompteCourant(0, "Jérémie", 500);
            comptes[0].Crediter(100);
            comptes[0].Debiter(50);
            comptes[0].Debiter(20, comptes[1]);
            comptes[1].Crediter(100);
            comptes[0].Crediter(20, comptes[1]);
            comptes[2].Debiter(500);
            comptes[2].Debiter(200, comptes[0]);
            // Affichage du solde de chaque compte:
            foreach (Compte c in comptes)
                c.AfficheSolde();
            Console.WriteLine("");
            // Affichage du détail de chaque compte:
            foreach (Compte c in comptes)
                c.Affichage();
        }


les classes:
Code : C#
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
public abstract class Compte
    {
        public double Solde { get; protected set; }
        public string Propriétaire { get; set; }

        private List<string> operations;

        // Constructeur
        public Compte(double solde, string proprietaire)
        {
            this.operations = new List<string>();
            this.Solde = solde;
            this.Propriétaire = proprietaire;
        }

        public virtual void Crediter(double somme)
        {
            this.Solde += somme;
            this.operations.Add("+"+somme);
        }

        public virtual void Crediter(double somme, Compte compte)
        {
            this.Solde += somme;
            compte.Debiter(somme);
            this.operations.Add("+" + somme);
        }

        public void Debiter(double somme)
        {
            this.Solde -= somme;
            this.operations.Add("-" + somme);
        }

        public void Debiter(double somme, Compte compte)
        {
            this.Solde -= somme;
            compte.Crediter(somme);
            this.operations.Add("-" + somme);
        }

        public abstract void AfficheSolde();

        public virtual void Affichage()
        {
            Console.WriteLine("Opérations:\n");
            foreach (string operation in operations)
                Console.WriteLine("\t" + operation);
        }
    }

    public class CompteCourant : Compte
    {
        public int Decouvert { get; private set; }

        // Constructeur
        public CompteCourant(double solde, string proprietaire, int decouvert)
            : base(solde, proprietaire)
        {
            this.Decouvert = decouvert;
        }

        public override void AfficheSolde()
        {
            Console.WriteLine("Solde compte courant de "+Propriétaire+": "+Solde);
        }

        public override void Affichage()
        {
            Console.WriteLine("Résumé du compte de " + Propriétaire);
            Console.WriteLine("*******************************************");
            Console.WriteLine("Compte courant de " + Propriétaire);
            Console.WriteLine("\tSolde: " + Solde);
            Console.WriteLine("\tDécouvert autorisé: " + Decouvert+"\n\n");
            base.Affichage();
            Console.WriteLine("*******************************************\n\n");
        }
    }

    public class CompteEpargne : Compte
    {
        public double Abondement { get; set; }

        // Constructeur
        public CompteEpargne(double solde, string proprietaire, double abondement)
            : base(solde, proprietaire)
        {
            this.Abondement = abondement;
        }

        public override void Crediter(double somme)
        {
            base.Crediter(somme);
            this.Solde += somme * Abondement;
        }

        public override void Crediter(double somme, Compte compte)
        {
            base.Crediter(somme, compte);
            this.Solde += somme * Abondement;
        }

        public override void AfficheSolde()
        {
            Console.WriteLine("Solde compte Epargne de "+ Propriétaire+ ": "+Solde);
        }

        public override void Affichage()
        {
            Console.WriteLine("Résumé du compte de " + Propriétaire);
            Console.WriteLine("*******************************************");
            Console.WriteLine("Compte épargne de " + Propriétaire);
            Console.WriteLine("\tSolde: " + Solde);
            Console.WriteLine("\tTaux d'abondement: " + Abondement + "\n\n");
            base.Affichage();
            Console.WriteLine("*******************************************\n\n");
        }
    }


Sur les grandes lignes, il semble que j'ai fait a peu près pareil que dans la correction, a savoir, la classe abstraite Compte, les 2 classes CompteCourant et CompteEpargne, la redéfinition de méthodes, ... Mais dans le détail, ça reste quand même assez différent.
A part pour le taux d'abondement ou j'ai du mal comprendre l'énoncé (ma méthode calcule le %tage de la somme créditée), les résultats obtenus semblent équivalents.

Du coups j'ai du mal a comparer avec la correction, est ce qu'il y a des choses qui sont mal faites ou non-conventionnelles dans mon code ?
Hors ligne nico.pyright # Posté le 30/03/2012 à 13:58:33
Groupe : Auteurs

A vue de nez, je n'ai rien à redire sur ton code, pas de souci :)
 
Hors ligne gfox78 # Posté le 23/04/2012 à 18:30:20
Avatar

Avis : Très bon

Ville : Marcq
Pays : France métropolitaine

Bonjour,
J'ai terminé la première partie du TP, non sans m'inspirer de quelques solutions et faire beaucoup de tentatives avortées déclenchant les insultes du compilateur.
En capitaliste impitoyable, j'ai ajouté des pénalités pour dépassement du découvert autorisé. J'ai aussi ajouté un commentaire justificatif pour chaque opération. Pour faire apparaître les intérêts ou les pénalités j'ai ajouté la méthode 'Apuration' aux classes 'compte', volontairement indépendante des méthodes d'affichage.
Secret (cliquez pour afficher)
Code : PHP
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<?php
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace TpBanque
{
    public abstract class Compte
    {

        protected string Proprio { get; set; }
        protected decimal Solde { get; set; } 
        private string Motif1 { get; set; }         // private par défaut
        private string Motif2 { get; set; }          
        private int numOper = 0;
        private List<string> Mouvements;
  
        // constructeur
        public Compte(string proprio, decimal solde, string motif1, string motif2)
        {
            Proprio = proprio;
            Solde = solde;
            Mouvements = new List<string>();
        }
         
        public void Crediter(decimal somme, string motif1)
        {
            this.Solde += somme;
            this.Mouvements.Add(string.Format("{0,02}) +{1,04}  \t {2}", ++numOper, somme, motif1)); 
        }

        public void Crediter(decimal somme, Compte aqui, string motif1, string motif2)
        {
            this.Solde += somme;              //this pour plus de lisibilité
            aqui.Debiter(somme, motif2);
            this.Mouvements.Add(string.Format("{0,02}) +{1,04}  \t {2}", ++numOper, somme, motif1));
        }

        public void Debiter(decimal somme, string motif1)
        {
            Solde -= somme;
            this.Mouvements.Add(string.Format("{0,02}) -{1,04}  \t {2}", ++numOper, somme, motif1));
        }

        public void Debiter(decimal somme, Compte pourqui, string motif1, string motif2)
        {            
            this.Solde -= somme;
            pourqui.Crediter(somme, motif2);
            this.Mouvements.Add(string.Format("{0,02}) -{1,04}  \t {2}", ++numOper, somme, motif1));
        }
        public virtual void Apuration() { }
        public virtual void SoldeAffiche() { }
        public virtual void ResumeAffiche()
        {
            foreach (string mvt in Mouvements)
            {
                Console.WriteLine(mvt);
            }
        }
    }

    public class CompteCt : Compte
    {
        private decimal Decouvert { get; set; }
        private double T_Agiot { get; set; }
        private int tauxP;
        // constructeur
        public CompteCt(string proprio, decimal solde, decimal decouvert, double t_Agiot, string motif1, string motif2)
            : base(proprio, solde, motif1, motif2)
        {
            T_Agiot = t_Agiot;
            tauxP = (int)(t_Agiot * 100);
            Decouvert = decouvert;
        }
        
       public override void Apuration()
        {
            if (Solde < -Decouvert)
            {
                Debiter((decimal)(-(double)((Decouvert + Solde)) * T_Agiot), "pénalité de dépassement du découvert");
            }
        } 

        public override void SoldeAffiche()
        {
            Console.WriteLine("Solde du Compte courant " + Proprio + " :" + Solde);
        }
        public override void ResumeAffiche()
        {
            Console.WriteLine("Compte courant de : {0} , découvert autorisé : {1} (pénalité: {2}%)", Proprio, Decouvert, (T_Agiot*1000.0)/10);           
            Console.WriteLine("Solde: " + Solde);
            base.ResumeAffiche();
            Console.WriteLine("---------------------------------------------------------------------");
        }
    } 

    public class CompteEp : Compte
    {
        private double T_Interet { get; set; }
        // constructeur
        public CompteEp(string proprio, decimal solde, double t_Interet, string motif1, string motif2)
            : base(proprio, solde, motif1, motif2)
        {
            T_Interet = t_Interet;
        }

        public override void Apuration()
        {
            Crediter((decimal)((double)(Solde) * T_Interet), "ajout des intérêts");
        }

        public override void SoldeAffiche()
        {
            Console.WriteLine("Solde du Compte d'épargne " + Proprio + " :" + Solde);
        }

        public override void ResumeAffiche()
        {            
            Console.WriteLine("Compte d'épargne de : {0} , taux d'intérêt : {1}%", Proprio, (T_Interet*1000.0)/10);
            Console.WriteLine("Solde: " + Solde);
            base.ResumeAffiche();
            Console.WriteLine("---------------------------------------------------------------------");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            decimal decouvert;             //declaration pour plus de lisibilité
            decimal solde = 0;
            double tauxInteret = 0.02;
            double tauxPenalite = 0.055;
            
            List<Compte> listeCompte = new List<Compte>();
            decouvert = 2000;
            CompteCt courantGuy = new CompteCt("Guy", solde, decouvert, tauxPenalite, "motif1", "motif2"); 
            decouvert = 500;
            CompteCt courantEric = new CompteCt("Eric", solde, decouvert, tauxPenalite, "motif1", "motif2"); 
            CompteEp epargneGuy = new CompteEp("Guy", solde, tauxInteret, "motif1", "motif2");    
            listeCompte.Add(courantGuy);
            listeCompte.Add(epargneGuy);
            listeCompte.Add(courantEric);

            courantGuy.Crediter(100, "versement de mon salaire");  
            courantGuy.Debiter(50, "achat de carburant");    
            epargneGuy.Crediter(20, courantGuy, "début épargne", "vers mon compte épargne");  
            epargneGuy.Crediter(100, "cadeau banque"); 
            epargneGuy.Debiter(20, courantGuy, "retrait épargne", "vient de mon compte d'éparne");
            courantEric.Debiter(500, "achat d'un ordinateur");  
            courantEric.Debiter(200, courantGuy, "payer ma dette à Guy", "remboursement d'Eric"); 
 
            foreach (Compte cpt in listeCompte)
            {
                cpt.Apuration();
            }
            foreach (Compte cpt in listeCompte)
            {
                cpt.SoldeAffiche();
            }
            Console.WriteLine("*********************************************************************");
            foreach (Compte cpt in listeCompte)
            {
                cpt.ResumeAffiche();
            }
        }
    }
}
?>

Je n'ai pas bien compris l'énoncé la 2ème partie du TP et j'ai été contraint de lire la solution avant de m'y mettre.
A cette occasion, j'ai relevé deux petites erreurs:
1) Le taux de 2.75% de l'énoncé devient 0.275 (27.5%) dans l'exemple.
2) Les deux derniers alinéas de la fin du chapitre sont répétés deux fois.

Voir tous les commentaires