Explorez la programmation orientée objet avec des classes et des objets

Dans ce didacticiel, vous allez créer une application console et voir les fonctionnalités orientées objet de base qui font partie du langage C#.

Prérequis

Créer votre application

Dans une fenêtre du terminal, créez un répertoire nommé Classes. Vous y créerez votre application. Sélectionnez ce répertoire et tapez dotnet new console dans la fenêtre de console. Cette commande crée votre application. Ouvrez Program.cs. Il doit se présenter comme suit :

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

Dans ce tutoriel, vous allez créer des types qui représentent un compte bancaire. Les développeurs définissent généralement chaque classe dans un fichier texte différent. Cela simplifie la gestion au fur et à mesure qu’un programme augmente en taille. Créez un fichier nommé BankAccount.cs dans le répertoire Classes.

Ce fichier contiendra la définition d’un compte bancaire. La programmation orientée objet organise le code en créant des types sous la forme de classes. Ces classes contiennent le code qui représente une entité spécifique. La classe BankAccount représente un compte bancaire. Le code implémente des opérations spécifiques à travers des méthodes et des propriétés. Dans ce tutoriel, le compte bancaire prend en charge le comportement suivant :

  1. Il contient un numéro à 10 chiffres qui identifie le compte bancaire de manière unique.
  2. Il contient une chaîne qui stocke le nom du ou des détenteurs.
  3. Le solde peut être récupéré.
  4. Il accepte les dépôts.
  5. Il accepte les retraits.
  6. Le solde initial doit être positif.
  7. Les retraits ne peuvent pas générer un solde négatif.

Définir le type de compte bancaire

Vous pouvez commencer par créer les éléments de base d’une classe définissant ce comportement. Créez un fichier à l’aide de la commande File:New. Nommez-le BankAccount.cs. Ajoutez le code suivant dans le fichier BankAccount.cs :

namespace Classes;

public class BankAccount
{
    public string Number { get; }
    public string Owner { get; set; }
    public decimal Balance { get; }

    public void MakeDeposit(decimal amount, DateTime date, string note)
    {
    }

    public void MakeWithdrawal(decimal amount, DateTime date, string note)
    {
    }
}

Avant de poursuivre, examinons ce que vous venez de créer. La déclaration namespace permet d’organiser logiquement votre code. Ce tutoriel étant relativement petit, vous allez placer tout le code dans un même espace de noms.

public class BankAccount définit la classe, ou le type, que vous créez. Tout ce qui est situé entre { et } après la déclaration de classe définit l’état et le comportement de la classe. La classe BankAccount a cinq membres. Les trois premiers sont des propriétés. Les propriétés sont des éléments de données qui peuvent avoir un code qui applique la validation ou d’autres règles. Les deux derniers sont des méthodes. Les méthodes sont des blocs de code qui effectuent une fonction unique. La lecture des noms de chacun des membres doit fournir suffisamment d’informations pour vous permettre (ou à tout autre développeur) de comprendre ce que fait la classe.

Ouvrir un nouveau compte

La première fonctionnalité à implémenter est l’ouverture d’un compte bancaire. Quand un client ouvre un compte, il doit fournir un solde initial, ainsi que des informations sur le ou les détenteurs du compte.

La création d’un objet de type BankAccount implique la définition d’un constructeur qui affecte ces valeurs. Un constructeur est un membre qui porte le même nom que la classe. Il est utilisé pour initialiser des objets de ce type de classe. Ajoutez le constructeur suivant au type BankAccount. Placez le code suivant au-dessus de la déclaration de MakeDeposit :

public BankAccount(string name, decimal initialBalance)
{
    this.Owner = name;
    this.Balance = initialBalance;
}

Le code précédent identifie les propriétés de l’objet en cours de construction en incluant le qualificateur this. Ce qualificateur est généralement facultatif et omis. Vous auriez également pu écrire :

public BankAccount(string name, decimal initialBalance)
{
    Owner = name;
    Balance = initialBalance;
}

Le qualificateur this est requis uniquement quand une variable ou un paramètre local porte le même nom que ce champ ou cette propriété. Le qualificateur this est omis dans le reste de cet article, sauf s’il est nécessaire.

Les constructeurs sont appelés quand vous créez un objet à l’aide de new. Remplacez la ligne Console.WriteLine("Hello World!"); dans Program.cs par le code suivant (remplacez <name> par votre nom) :

using Classes;

var account = new BankAccount("<name>", 1000);
Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} initial balance.");

Exécutons ce que vous avez créé à ce stade. Si vous utilisez Visual Studio, sélectionnez Démarrer sans débogage dans le menu Déboguer. Si vous utilisez une ligne de commande, tapez dotnet run dans le répertoire où vous avez créé votre projet.

Avez-vous remarqué que le numéro de compte est vide ? L’heure est venue de traiter ce point. Le numéro de compte doit être assigné une fois l’objet construit. Mais ce ne devrait pas être à l’appelant de le créer. Le code de la classe BankAccount devrait savoir comment assigner de nouveaux numéros de compte. Un moyen simple consiste à commencer avec un nombre à 10 chiffres. Incrémentez-le chaque fois qu’un compte est créé. Enfin, stockez le numéro de compte actuel quand un objet est construit.

Ajoutez une déclaration de membre à la classe BankAccount. Placez la ligne de code suivante après l’accolade ouvrante { au début de la classe BankAccount :

private static int s_accountNumberSeed = 1234567890;

accountNumberSeed est un membre de données. Celui-ci est private, ce qui signifie qu’il est uniquement accessible par code dans la classe BankAccount. C’est un moyen de séparer les responsabilités publiques (comme le fait de disposer d’un numéro de compte) de l’implémentation privée (c’est-à-dire la façon dont les numéros de compte sont générés). Il est également static, ce qui signifie qu’il est partagé par tous les objets BankAccount. La valeur d’une variable non statique est unique pour chaque instance de l’objet BankAccount. Le accountNumberSeed est un private static champ et a donc le s_ préfixe conformément aux conventions de nommage C#. Le s dénotant static et _ dénotant private le champ. Ajoutez les deux lignes suivantes au constructeur pour affecter le numéro de compte. Placez-les après la ligne qui stipule this.Balance = initialBalance :

Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;

Tapez dotnet run pour afficher les résultats.

Créer des dépôts et des retraits

La classe de votre compte bancaire doit accepter les dépôts et les retraits pour fonctionner correctement. Nous allons implémenter des dépôts et des retraits en créant un journal de chaque transaction du compte. Le suivi de chaque transaction présente plusieurs avantages par rapport à la simple mise à jour du solde à chaque transaction. L’historique peut être utilisé pour auditer toutes les transactions et gérer les soldes au quotidien. Le calcul du solde à partir de l’historique de toutes les transactions, lorsque nécessaire, garantit que toute erreur corrigée dans une transaction individuelle sera reflétée correctement dans le solde lors du calcul suivant.

Commençons par créer un type pour représenter une transaction. La transaction est un type simple qui n’a aucune responsabilité. Il a besoin de quelques propriétés. Créez un fichier nommé Transaction.cs. Ajoutez-lui le code suivant :

namespace Classes;

public class Transaction
{
    public decimal Amount { get; }
    public DateTime Date { get; }
    public string Notes { get; }

    public Transaction(decimal amount, DateTime date, string note)
    {
        Amount = amount;
        Date = date;
        Notes = note;
    }
}

Nous allons maintenant ajouter une List<T> d’objets Transaction à la classe BankAccount. Ajoutez la déclaration suivante après le constructeur dans votre fichier BankAccount.cs :

private List<Transaction> _allTransactions = new List<Transaction>();

À présent, nous allons calculer correctement Balance. Le solde actuel peut être obtenu en additionnant les valeurs de toutes les transactions. Dans l’état actuel du code, vous ne pouvez obtenir que le solde initial du compte. Vous devrez donc mettre à jour la propriété Balance. Remplacez la ligne public decimal Balance { get; } dans BankAccount.cs par le code suivant :

public decimal Balance
{
    get
    {
        decimal balance = 0;
        foreach (var item in _allTransactions)
        {
            balance += item.Amount;
        }

        return balance;
    }
}

Cet exemple montre un aspect important des propriétés. Vous calculez à présent le solde quand un autre programmeur en demande la valeur. Votre calcul énumère toutes les transactions et retourne la somme en tant que solde actuel.

Implémentez ensuite les méthodes MakeDeposit et MakeWithdrawal. Ces méthodes appliquent les deux règles finales : le solde initial doit être positif et un retrait ne doit pas créer un solde négatif.

Ces règles introduisent le concept d’exceptions. La manière standard d’indiquer qu’une méthode ne peut pas effectuer correctement son travail consiste à lever une exception. Le type d’exception et le message associé décrivent l’erreur. Ici, la méthode MakeDeposit lève une exception si le montant du dépôt n’est pas supérieur à 0. La méthode MakeWithdrawal lève une exception si le montant du retrait n’est pas supérieur à 0 ou si l’application du retrait entraîne un solde négatif. Ajoutez le code suivant après la déclaration de la liste _allTransactions :

public void MakeDeposit(decimal amount, DateTime date, string note)
{
    if (amount <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
    }
    var deposit = new Transaction(amount, date, note);
    _allTransactions.Add(deposit);
}

public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
    if (amount <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
    }
    if (Balance - amount < 0)
    {
        throw new InvalidOperationException("Not sufficient funds for this withdrawal");
    }
    var withdrawal = new Transaction(-amount, date, note);
    _allTransactions.Add(withdrawal);
}

L’instruction throwlève une exception. L’exécution du bloc actuel se termine et le contrôle est transféré au premier bloc correspondant catch trouvé dans la pile des appels. Vous ajouterez un bloc catch pour tester ce code un peu plus tard.

Le constructeur devrait obtenir une modification lui permettant d’ajouter une transaction initiale au lieu de mettre directement le solde à jour. Étant donné que vous avez déjà écrit la méthode MakeDeposit, appelez-la à partir de votre constructeur. Le constructeur terminé doit être semblable à ce qui suit :

public BankAccount(string name, decimal initialBalance)
{
    Number = s_accountNumberSeed.ToString();
    s_accountNumberSeed++;

    Owner = name;
    MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}

DateTime.Now est une propriété qui retourne la date et l'heure actuelles. Testez ce code en ajoutant quelques dépôts et retraits dans votre méthode Main, à la suite du code qui crée un nouveau BankAccount :

account.MakeWithdrawal(500, DateTime.Now, "Rent payment");
Console.WriteLine(account.Balance);
account.MakeDeposit(100, DateTime.Now, "Friend paid me back");
Console.WriteLine(account.Balance);

Ensuite, vérifiez que vous interceptez des conditions d’erreur en essayant de créer un compte avec un solde négatif. Ajoutez le code suivant après le code précédent que vous venez d’ajouter :

// Test that the initial balances must be positive.
BankAccount invalidAccount;
try
{
    invalidAccount = new BankAccount("invalid", -55);
}
catch (ArgumentOutOfRangeException e)
{
    Console.WriteLine("Exception caught creating account with negative balance");
    Console.WriteLine(e.ToString());
    return;
}

Vous utilisez l’instruction try-catch pour marquer un bloc de code pouvant lever des exceptions et intercepter les erreurs que vous attendez. Vous pouvez utiliser la même technique pour tester le code qui lève une exception pour un solde négatif. Ajoutez le code suivant avant la déclaration de invalidAccount dans votre méthode Main :

// Test for a negative balance.
try
{
    account.MakeWithdrawal(750, DateTime.Now, "Attempt to overdraw");
}
catch (InvalidOperationException e)
{
    Console.WriteLine("Exception caught trying to overdraw");
    Console.WriteLine(e.ToString());
}

Enregistrez le fichier et tapez dotnet run pour effectuer un essai.

Test : consigner toutes les transactions

Pour terminer ce tutoriel, vous pouvez écrire la méthode GetAccountHistory qui crée une string pour l’historique des transactions. Ajoutez cette méthode au type BankAccount :

public string GetAccountHistory()
{
    var report = new System.Text.StringBuilder();

    decimal balance = 0;
    report.AppendLine("Date\t\tAmount\tBalance\tNote");
    foreach (var item in _allTransactions)
    {
        balance += item.Amount;
        report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
    }

    return report.ToString();
}

L’historique utilise la classe StringBuilder pour mettre en forme une chaîne contenant une ligne pour chaque transaction. Vous avez vu le code de mise en forme de chaîne précédemment dans ces tutoriels. Vous pouvez observer le nouveau caractère \t. Celui-ci insère une tabulation pour mettre en forme la sortie.

Ajoutez cette ligne pour effectuer un essai dans Program.cs :

Console.WriteLine(account.GetAccountHistory());

Exécutez votre programme pour voir les résultats.

Étapes suivantes

Si vous êtes bloqué, vous pouvez afficher la source de ce didacticiel dans notre dépôt GitHub.

Vous pouvez continuer avec le didacticiel sur la programmation orientée objet.

Pour en savoir plus sur ces concepts, consultez les articles :