Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
C# est un langage de programmation orienté objet. Les quatre principes de base de la programmation orientée objet sont les suivants :
- Abstraction Modélisation des attributs et interactions pertinents des entités en tant que classes pour définir une représentation abstraite d’un système.
- Encapsulation Masquage de l’état interne et des fonctionnalités d’un objet et autorise uniquement l’accès via un ensemble public de fonctions.
- Héritage Possibilité de créer de nouvelles abstractions basées sur des abstractions existantes.
- Polymorphisme Possibilité d’implémenter des propriétés ou des méthodes héritées de différentes manières entre plusieurs abstractions.
Dans le tutoriel précédent, introduction aux classes, vous avez vu à la fois l’abstraction et l’encapsulation. La BankAccount
classe a fourni une abstraction pour le concept d’un compte bancaire. Vous pouvez modifier son implémentation sans affecter l’un du code qui a utilisé la BankAccount
classe. Les classes BankAccount
et Transaction
fournissent l'encapsulation des composants nécessaires pour décrire ces concepts dans le code.
Dans ce tutoriel, vous allez étendre cette application pour utiliser l’héritage et le polymorphisme pour ajouter de nouvelles fonctionnalités. Vous allez également ajouter des fonctionnalités à la BankAccount
classe, en tirant parti des techniques d’abstraction et d’encapsulation que vous avez apprises dans le tutoriel précédent.
Créer différents types de comptes
Après avoir créé ce programme, vous obtenez des demandes pour y ajouter des fonctionnalités. Il fonctionne très bien dans la situation où il n’y a qu’un seul type de compte bancaire. Au fil du temps, les besoins changent et les types de comptes associés sont demandés :
- Un compte de revenu d’intérêts qui accumule des intérêts à la fin de chaque mois.
- Une marge de crédit qui peut avoir un solde négatif, mais quand il y a un solde, il y a des frais d’intérêt chaque mois.
- Un compte de carte cadeau prépayée qui commence par un dépôt unique et qui ne peut être utilisé que pour faire des paiements. Il peut être rempli une fois au début de chaque mois.
Tous ces différents comptes sont similaires à BankAccount
la classe définie dans le didacticiel précédent. Vous pouvez copier ce code, renommer les classes et apporter des modifications. Cette technique fonctionnerait à court terme, mais elle nécessiterait plus de travail avec le temps. Toutes les modifications sont copiées dans toutes les classes affectées.
Au lieu de cela, vous pouvez créer de nouveaux types de comptes bancaires qui héritent des méthodes et des données de la BankAccount
classe créée dans le tutoriel précédent. Ces nouvelles classes peuvent étendre la BankAccount
classe avec le comportement spécifique nécessaire pour chaque type :
public class InterestEarningAccount : BankAccount
{
}
public class LineOfCreditAccount : BankAccount
{
}
public class GiftCardAccount : BankAccount
{
}
Chacune de ces classes hérite du comportement partagé de leur classe de base partagée, la BankAccount
classe. Écrivez les implémentations pour les fonctionnalités nouvelles et différentes dans chacune des classes dérivées. Ces classes dérivées ont déjà tout le comportement défini dans la BankAccount
classe.
Il est recommandé de créer chaque classe dans un fichier source différent. Dans Visual Studio, vous pouvez cliquer avec le bouton droit sur le projet, puis sélectionner ajouter une classe pour ajouter une nouvelle classe dans un nouveau fichier. Dans Visual Studio Code, sélectionnez Fichier , puis Nouveau pour créer un fichier source. Dans l’un ou l’autre outil, nommez le fichier pour qu’il corresponde à la classe : InterestEarningAccount.cs, LineOfCreditAccount.cs et GiftCardAccount.cs.
Lorsque vous créez les classes comme indiqué dans l’exemple précédent, vous constaterez qu’aucune de vos classes dérivées n’est compilée. Un constructeur est responsable de l’initialisation d’un objet. Un constructeur de classe dérivée doit initialiser la classe dérivée et fournir des instructions sur l’initialisation de l’objet de classe de base inclus dans la classe dérivée. L’initialisation appropriée se produit normalement sans code supplémentaire. La BankAccount
classe déclare un constructeur public avec la signature suivante :
public BankAccount(string name, decimal initialBalance)
Le compilateur ne génère pas de constructeur par défaut lorsque vous définissez un constructeur vous-même. Cela signifie que chaque classe dérivée doit appeler explicitement ce constructeur. Vous déclarez un constructeur qui peut passer des arguments au constructeur de classe de base. Le code suivant montre le constructeur pour l'objet InterestEarningAccount
.
public InterestEarningAccount(string name, decimal initialBalance) : base(name, initialBalance)
{
}
Les paramètres de ce nouveau constructeur correspondent au type de paramètre et aux noms du constructeur de classe de base. Vous utilisez la : base()
syntaxe pour indiquer un appel à un constructeur de classe de base. Certaines classes définissent plusieurs constructeurs et cette syntaxe vous permet de choisir le constructeur de classe de base que vous appelez. Une fois que vous avez mis à jour les constructeurs, vous pouvez développer le code pour chacune des classes dérivées. Les conditions requises pour les nouvelles classes peuvent être indiquées comme suit :
- Un compte de revenu d’intérêt :
- Obtiendra un crédit de 2 % du solde de fin de mois.
- Une ligne de crédit :
- Peut avoir un solde négatif, mais pas être supérieur à la valeur absolue que la limite de crédit.
- Entraîne des frais d’intérêt chaque mois où le solde de fin de mois n’est pas 0.
- Entraîne des frais sur chaque retrait qui dépasse la limite de crédit.
- Un compte de carte cadeau :
- Peut être renseigné avec un montant spécifié une fois par mois, le dernier jour du mois.
Vous pouvez voir que les trois de ces types de comptes ont une action qui prend place à la fin de chaque mois. Toutefois, chaque type de compte effectue des tâches différentes. Vous utilisez le polymorphisme pour implémenter ce code. Créez une méthode unique virtual
dans la BankAccount
classe :
public virtual void PerformMonthEndTransactions() { }
Le code précédent montre comment utiliser le virtual
mot clé pour déclarer une méthode dans la classe de base pour laquelle une classe dérivée peut fournir une implémentation différente. Une virtual
méthode est une méthode où toute classe dérivée peut choisir de réexémettre. Les classes dérivées utilisent le override
mot clé pour définir la nouvelle implémentation. En règle générale, vous appelez ceci « redéfinition de l’implémentation de classe de base ». Le virtual
mot clé spécifie que les classes dérivées peuvent remplacer le comportement. Vous pouvez également déclarer abstract
des méthodes où les classes dérivées doivent remplacer le comportement. La classe de base ne fournit pas d’implémentation pour une abstract
méthode. Ensuite, vous devez définir l’implémentation pour deux des nouvelles classes que vous avez créées. Commencez par :InterestEarningAccount
public override void PerformMonthEndTransactions()
{
if (Balance > 500m)
{
decimal interest = Balance * 0.02m;
MakeDeposit(interest, DateTime.Now, "apply monthly interest");
}
}
Ajoutez le code suivant au LineOfCreditAccount
. Le code annule le solde pour calculer une charge d’intérêt positive qui est retirée du compte :
public override void PerformMonthEndTransactions()
{
if (Balance < 0)
{
// Negate the balance to get a positive interest charge:
decimal interest = -Balance * 0.07m;
MakeWithdrawal(interest, DateTime.Now, "Charge monthly interest");
}
}
La GiftCardAccount
classe a besoin de deux modifications pour implémenter ses fonctionnalités de fin de mois. Tout d’abord, modifiez le constructeur pour inclure une quantité facultative à ajouter chaque mois :
private readonly decimal _monthlyDeposit = 0m;
public GiftCardAccount(string name, decimal initialBalance, decimal monthlyDeposit = 0) : base(name, initialBalance)
=> _monthlyDeposit = monthlyDeposit;
Le constructeur fournit une valeur par défaut pour monthlyDeposit
, permettant ainsi aux appelants d'omettre 0
s'il n'y a pas de dépôt mensuel. Ensuite, remplacez la PerformMonthEndTransactions
méthode pour ajouter le dépôt mensuel, s’il a été défini sur une valeur différente de zéro dans le constructeur :
public override void PerformMonthEndTransactions()
{
if (_monthlyDeposit != 0)
{
MakeDeposit(_monthlyDeposit, DateTime.Now, "Add monthly deposit");
}
}
Le remplacement applique l’ensemble des versements mensuels dans le constructeur. Ajoutez le code suivant à la méthode Main
pour tester ces modifications pour les GiftCardAccount
et les InterestEarningAccount
.
var giftCard = new GiftCardAccount("gift card", 100, 50);
giftCard.MakeWithdrawal(20, DateTime.Now, "get expensive coffee");
giftCard.MakeWithdrawal(50, DateTime.Now, "buy groceries");
giftCard.PerformMonthEndTransactions();
// can make additional deposits:
giftCard.MakeDeposit(27.50m, DateTime.Now, "add some additional spending money");
Console.WriteLine(giftCard.GetAccountHistory());
var savings = new InterestEarningAccount("savings account", 10000);
savings.MakeDeposit(750, DateTime.Now, "save some money");
savings.MakeDeposit(1250, DateTime.Now, "Add more savings");
savings.MakeWithdrawal(250, DateTime.Now, "Needed to pay monthly bills");
savings.PerformMonthEndTransactions();
Console.WriteLine(savings.GetAccountHistory());
Vérifiez les résultats. À présent, ajoutez un ensemble similaire de code de test pour :LineOfCreditAccount
var lineOfCredit = new LineOfCreditAccount("line of credit", 0);
// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());
Lorsque vous ajoutez le code précédent et exécutez le programme, vous verrez quelque chose comme l’erreur suivante :
Unhandled exception. System.ArgumentOutOfRangeException: Amount of deposit must be positive (Parameter 'amount')
at OOProgramming.BankAccount.MakeDeposit(Decimal amount, DateTime date, String note) in BankAccount.cs:line 42
at OOProgramming.BankAccount..ctor(String name, Decimal initialBalance) in BankAccount.cs:line 31
at OOProgramming.LineOfCreditAccount..ctor(String name, Decimal initialBalance) in LineOfCreditAccount.cs:line 9
at OOProgramming.Program.Main(String[] args) in Program.cs:line 29
Remarque
La sortie réelle inclut le chemin complet du dossier avec le projet. Les noms des dossiers ont été omis pour la concision. En outre, selon votre format de code, les numéros de ligne peuvent être légèrement différents.
Ce code échoue, car le BankAccount
principe que l’équilibre initial doit être supérieur à 0. Une autre hypothèse cuite dans la BankAccount
classe est que l’équilibre ne peut pas aller négatif. Au lieu de cela, tout retrait qui entraîne un découvert du compte est rejeté. Ces deux hypothèses doivent changer. Le compte de ligne de crédit commence à 0 et aura généralement un solde négatif. En outre, si un client emprunte trop d’argent, il entraîne des frais. La transaction est acceptée, cela coûte juste plus cher. La première règle peut être implémentée en ajoutant un argument facultatif au BankAccount
constructeur qui spécifie l’équilibre minimal. La valeur par défaut est 0
. La deuxième règle nécessite un mécanisme qui permet aux classes dérivées de modifier l’algorithme par défaut. Dans un sens, la classe de base « demande » au type dérivé ce qui doit se produire en cas de découvert. Le comportement par défaut consiste à rejeter la transaction en levant une exception.
Commençons par ajouter un deuxième constructeur qui inclut un paramètre facultatif minimumBalance
. Ce nouveau constructeur effectue toutes les actions effectuées par le constructeur existant. En outre, il définit la propriété d’équilibre minimal. Vous pouvez copier le corps du constructeur existant, mais cela signifie que deux emplacements doivent être modifiés à l’avenir. Au lieu de cela, vous pouvez utiliser le chaînage du constructeur pour qu’un constructeur en appelle un autre. Le code suivant montre les deux constructeurs et le nouveau champ supplémentaire :
private readonly decimal _minimumBalance;
public BankAccount(string name, decimal initialBalance) : this(name, initialBalance, 0) { }
public BankAccount(string name, decimal initialBalance, decimal minimumBalance)
{
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;
Owner = name;
_minimumBalance = minimumBalance;
if (initialBalance > 0)
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}
Le code précédent montre deux nouvelles techniques. Tout d’abord, le minimumBalance
champ est marqué comme readonly
. Cela signifie que la valeur ne peut pas être modifiée une fois l’objet construit. Une fois qu’un BankAccount
a été créé, le minimumBalance
fichier ne peut pas changer. Deuxièmement, le constructeur qui prend deux paramètres utilise : this(name, initialBalance, 0) { }
comme implémentation. L’expression : this()
appelle l’autre constructeur, celui avec trois paramètres. Cette technique vous permet d’avoir une implémentation unique pour initialiser un objet même si le code client peut choisir l’un des nombreux constructeurs.
Cette implémentation appelle MakeDeposit
uniquement si le solde initial est supérieur à 0
. Cela préserve la règle selon laquelle les dépôts doivent être positifs, mais laisse le compte de crédit ouvert avec un 0
solde.
Maintenant que la BankAccount
classe a un champ en lecture seule pour le solde minimal, la modification finale consiste à changer le code en dur 0
par minimumBalance
dans la méthode MakeWithdrawal
.
if (Balance - amount < _minimumBalance)
Après avoir étendu la BankAccount
classe, vous pouvez modifier le LineOfCreditAccount
constructeur pour appeler le nouveau constructeur de base, comme indiqué dans le code suivant :
public LineOfCreditAccount(string name, decimal initialBalance, decimal creditLimit) : base(name, initialBalance, -creditLimit)
{
}
Notez que le LineOfCreditAccount
constructeur modifie le signe du creditLimit
paramètre afin qu’il corresponde à la signification du minimumBalance
paramètre.
Différentes règles de dépassement
La dernière fonctionnalité à ajouter permet LineOfCreditAccount
de facturer des frais pour passer au-dessus de la limite de crédit au lieu de refuser la transaction.
Une technique consiste à définir une fonction virtuelle dans laquelle vous implémentez le comportement requis. La BankAccount
classe refactorise la MakeWithdrawal
méthode en deux méthodes. La nouvelle méthode réalise l'action spécifiée lorsque le retrait fait descendre le solde en dessous du minimum. La méthode existante MakeWithdrawal
a le code suivant :
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 < _minimumBalance)
{
throw new InvalidOperationException("Not sufficient funds for this withdrawal");
}
var withdrawal = new Transaction(-amount, date, note);
_allTransactions.Add(withdrawal);
}
Remplacez-le par le code suivant :
public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
}
Transaction? overdraftTransaction = CheckWithdrawalLimit(Balance - amount < _minimumBalance);
Transaction? withdrawal = new(-amount, date, note);
_allTransactions.Add(withdrawal);
if (overdraftTransaction != null)
_allTransactions.Add(overdraftTransaction);
}
protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn)
{
if (isOverdrawn)
{
throw new InvalidOperationException("Not sufficient funds for this withdrawal");
}
else
{
return default;
}
}
La méthode ajoutée est protected
, ce qui signifie qu’elle peut être appelée uniquement à partir de classes dérivées. Cette déclaration empêche d’autres clients d’appeler la méthode. C’est également virtual
pour que les classes dérivées puissent modifier le comportement. Le type de retour est .Transaction?
L’annotation ?
indique que la méthode peut retourner null
. Ajoutez l’implémentation suivante pour LineOfCreditAccount
facturer des frais lorsque la limite de retrait est dépassée :
protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) =>
isOverdrawn
? new Transaction(-20, DateTime.Now, "Apply overdraft fee")
: default;
Le remplacement retourne une transaction de frais lorsque le compte est à découvert. Si le retrait ne dépasse pas la limite, la méthode retourne une null
transaction. Cela indique qu’il n’y a pas de frais. Testez ces modifications en ajoutant le code suivant à votre Main
méthode dans la Program
classe :
var lineOfCredit = new LineOfCreditAccount("line of credit", 0, 2000);
// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());
Exécutez le programme et vérifiez les résultats.
Résumé
Si vous êtes bloqué, vous pouvez voir la source de ce didacticiel dans notre dépôt GitHub.
Ce didacticiel a montré la plupart des techniques utilisées dans la programmation Object-Oriented :
- Vous avez utilisé l’abstraction lorsque vous avez défini des classes pour chacun des différents types de comptes. Ces classes décrivent le comportement de ce type de compte.
- Vous avez utilisé l’encapsulation lorsque vous avez conservé de nombreux détails
private
dans chaque classe. - Vous avez utilisé l'héritage lorsque vous avez tiré parti de l'implémentation déjà créée dans la classe
BankAccount
pour réduire le code. - Vous avez utilisé polymorphisme lorsque vous avez créé
virtual
des méthodes que les classes dérivées pouvaient remplacer pour créer un comportement spécifique pour ce type de compte.