Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
C# ist eine objektorientierte Programmiersprache. Die vier Grundprinzipien der objektorientierten Programmierung sind:
- Abstraktion Modellieren der relevanten Attribute und Interaktionen von Entitäten als Klassen zum Definieren einer abstrakten Darstellung eines Systems.
- Verkapselung Ausblenden des internen Zustands und der Funktionalität eines Objekts und nur das Zulassen des Zugriffs über einen öffentlichen Satz von Funktionen.
- Erbschaft Möglichkeit, neue Abstraktionen basierend auf vorhandenen Abstraktionen zu erstellen.
- Polymorphismus Fähigkeit, geerbte Eigenschaften oder Methoden auf unterschiedliche Weise über mehrere Abstraktionen hinweg zu implementieren.
Im vorherigen Tutorial Einführung in Klassen wurden sowohl Abstraktion als auch Kapselung vorgestellt. Die BankAccount
Klasse lieferte eine Abstraktion für das Konzept eines Bankkontos. Sie können die Implementierung ändern, ohne dass sich dies auf den Code auswirkt, der die BankAccount
Klasse verwendet hat. Sowohl die BankAccount
-Klasse als auch die Transaction
-Klasse bieten eine Kapselung der benötigten Komponenten, um diese Konzepte im Code zu beschreiben.
In diesem Lernprogramm erweitern Sie diese Anwendung, um Vererbung und Polymorphismus zu verwenden, um neue Features hinzuzufügen. Außerdem fügen Sie der BankAccount
Klasse Features hinzu, wobei Sie die im vorherigen Lernprogramm gelernten Abstraktions - und Kapselungstechniken nutzen.
Erstellen unterschiedlicher Kontentypen
Nach dem Erstellen dieses Programms erhalten Sie Anforderungen zum Hinzufügen von Features. Es funktioniert hervorragend in der Situation, in der nur ein Bankkontotyp vorhanden ist. Im Laufe der Zeit ändern sich die Bedürfnisse, und es werden entsprechende Kontotypen angefordert.
- Ein zinsbringendes Konto, bei dem am Ende jedes Monats Zinsen anfallen.
- Eine Kreditlinie, die einen negativen Saldo haben kann, aber wenn ein Saldo vorhanden ist, gibt es jeden Monat eine Zinsgebühr.
- Ein vorausbezahltes Geschenkgutscheinkonto, das mit einer einzigen Einzahlung beginnt und nur abgezahlt werden kann. Es kann einmal zu Beginn jedes Monats ausgefüllt werden.
Alle diese verschiedenen Konten ähneln der klasse, die im vorherigen Lernprogramm definiert wurde BankAccount
. Sie können diesen Code kopieren, die Klassen umbenennen und Änderungen vornehmen. Diese Technik würde kurzfristig funktionieren, aber es wäre mehr Arbeit im Laufe der Zeit. Alle Änderungen werden in alle betroffenen Klassen kopiert.
Stattdessen können Sie neue Bankkontotypen erstellen, die Methoden und Daten von der BankAccount
Klasse erben, die im vorherigen Lernprogramm erstellt wurde. Diese neuen Klassen können die BankAccount
Klasse um das spezifische Verhalten erweitern, das für jeden Typ erforderlich ist:
public class InterestEarningAccount : BankAccount
{
}
public class LineOfCreditAccount : BankAccount
{
}
public class GiftCardAccount : BankAccount
{
}
Jede dieser Klassen erbt das freigegebene Verhalten von ihrer freigegebenen Basisklasse, der BankAccount
Klasse. Schreiben Sie die Implementierungen für neue und unterschiedliche Funktionen in jeder der abgeleiteten Klassen. Diese abgeleiteten Klassen weisen bereits alle in der BankAccount
Klasse definierten Verhaltensweisen auf.
Es empfiehlt sich, jede neue Klasse in einer anderen Quelldatei zu erstellen. In Visual Studio können Sie mit der rechten Maustaste auf das Projekt klicken und "Klasse hinzufügen" auswählen, um eine neue Klasse in einer neuen Datei hinzuzufügen. Wählen Sie in Visual Studio Code " Datei " und dann "Neu" aus, um eine neue Quelldatei zu erstellen. Benennen Sie in beiden Tools die Datei, die der Klasse entspricht: InterestEarningAccount.cs, LineOfCreditAccount.cs und GiftCardAccount.cs.
Wenn Sie die Klassen wie im vorherigen Beispiel gezeigt erstellen, stellen Sie fest, dass keine der abgeleiteten Klassen kompiliert wird. Ein Konstruktor ist für die Initialisierung eines Objekts verantwortlich. Ein abgeleiteter Klassenkonstruktor muss die abgeleitete Klasse initialisieren und Anweisungen zum Initialisieren des In der abgeleiteten Klasse enthaltenen Basisklassenobjekts bereitstellen. Die richtige Initialisierung erfolgt normalerweise ohne zusätzlichen Code. Die BankAccount
Klasse deklariert einen öffentlichen Konstruktor mit der folgenden Signatur:
public BankAccount(string name, decimal initialBalance)
Der Compiler generiert keinen Standardkonstruktor, wenn Sie einen Konstruktor selbst definieren. Dies bedeutet, dass jede abgeleitete Klasse diesen Konstruktor explizit aufrufen muss. Sie deklarieren einen Konstruktor, der Argumente an den Basisklassenkonstruktor übergeben kann. Der folgende Code zeigt den Konstruktor für den InterestEarningAccount
:
public InterestEarningAccount(string name, decimal initialBalance) : base(name, initialBalance)
{
}
Die Parameter für diesen neuen Konstruktor entsprechen dem Parametertyp und den Namen des Basisklassenkonstruktors. Sie verwenden die : base()
Syntax, um einen Aufruf eines Basisklassenkonstruktors anzugeben. Einige Klassen definieren mehrere Konstruktoren, und mit dieser Syntax können Sie auswählen, welchen Basisklassenkonstruktor Sie aufrufen. Nachdem Sie die Konstruktoren aktualisiert haben, können Sie den Code für jede der abgeleiteten Klassen entwickeln. Die Anforderungen für die neuen Klassen können wie folgt angegeben werden:
- Ein zinsbringendes Konto:
- Erhält eine Gutschrift von 2 % des Saldos am Monatsende.
- Kreditlinie:
- Kann einen negativen Saldo aufweisen, aber nicht größer als der Kreditgrenzwert.
- Jeden Monat wird eine Zinsbelastung anfallen, wenn der Monatsendstand nicht 0 ist.
- Es wird eine Gebühr für jeden Auszahlungsbetrag anfallen, der das Kreditlimit überschreitet.
- Ein Geschenkgutscheinkonto:
- Kann einmal pro Monat mit einem angegebenen Betrag am letzten Tag des Monats ausgefüllt werden.
Sie können sehen, dass alle drei dieser Kontotypen eine Aktion haben, die am Ende jedes Monats stattfindet. Jeder Kontotyp führt jedoch unterschiedliche Aufgaben aus. Sie verwenden Polymorphismus , um diesen Code zu implementieren. Erstellen Sie eine einzelne virtual
Methode in der BankAccount
Klasse:
public virtual void PerformMonthEndTransactions() { }
Der vorangehende Code zeigt, wie Sie das virtual
Schlüsselwort verwenden, um eine Methode in der Basisklasse zu deklarieren, für die eine abgeleitete Klasse möglicherweise eine andere Implementierung bereitstellen kann. Eine virtual
Methode ist eine Methode, bei der eine abgeleitete Klasse sich für eine erneute Umsetzung entscheiden kann. Die abgeleiteten Klassen verwenden das override
Schlüsselwort, um die neue Implementierung zu definieren. In der Regel bezeichnen Sie dies als "Außerkraftsetzung der Basisklassenimplementierung". Das virtual
Schlüsselwort gibt an, dass abgeleitete Klassen das Verhalten überschreiben können. Sie können auch Methoden deklarieren, bei denen abgeleitete Klassen das Verhalten überschreiben müssen abstract
. Die Basisklasse stellt keine Implementierung für eine abstract
Methode bereit. Als Nächstes müssen Sie die Implementierung für zwei der neuen Klassen definieren, die Sie erstellt haben. Beginnen Sie mit dem InterestEarningAccount
:
public override void PerformMonthEndTransactions()
{
if (Balance > 500m)
{
decimal interest = Balance * 0.02m;
MakeDeposit(interest, DateTime.Now, "apply monthly interest");
}
}
Fügen Sie den folgenden Code zu LineOfCreditAccount
hinzu. Der Code negiert den Saldo, um eine positive Zinsgebühr zu berechnen, die vom Konto zurückgezogen wird:
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");
}
}
Die GiftCardAccount
Klasse benötigt zwei Änderungen, um ihre Month-End-Funktionalität zu implementieren. Ändern Sie zunächst den Konstruktor so, dass er einen optionalen Betrag enthält, der jeden Monat hinzugefügt werden soll:
private readonly decimal _monthlyDeposit = 0m;
public GiftCardAccount(string name, decimal initialBalance, decimal monthlyDeposit = 0) : base(name, initialBalance)
=> _monthlyDeposit = monthlyDeposit;
Der Konstruktor stellt einen Standardwert für den monthlyDeposit
-Wert bereit, sodass Aufrufer keine 0
eingeben müssen, wenn keine monatliche Einzahlung erfolgt. Überschreiben Sie als nächstes die PerformMonthEndTransactions
-Methode, um die monatliche Einzahlung hinzuzufügen, falls sie im Konstruktor auf einen Wert ungleich 0 festgelegt wurde:
public override void PerformMonthEndTransactions()
{
if (_monthlyDeposit != 0)
{
MakeDeposit(_monthlyDeposit, DateTime.Now, "Add monthly deposit");
}
}
Durch die Überschreibung wird die im Konstruktor festgelegte monatliche Einzahlung angewendet. Fügen Sie der Main
-Methode den folgenden Code hinzu, um diese Änderungen für die GiftCardAccount
und die InterestEarningAccount
zu testen.
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());
Überprüfen Sie die Ergebnisse. Fügen Sie nun einen ähnlichen Testcodesatz für folgendes LineOfCreditAccount
hinzu:
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());
Wenn Sie den vorherigen Code hinzufügen und das Programm ausführen, wird etwa der folgende Fehler angezeigt:
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
Hinweis
Die tatsächliche Ausgabe enthält den vollständigen Pfad zum Ordner mit dem Projekt. Die Ordnernamen wurden aus Platzgründen weggelassen. Je nach Codeformat unterscheiden sich die Zeilennummern möglicherweise geringfügig.
Dieser Code schlägt fehl, da davon BankAccount
ausgegangen wird, dass der Anfangssaldo größer als 0 sein muss. Eine weitere Annahme, die in die BankAccount
Klasse eingegliedert ist, ist, dass das Gleichgewicht nicht negativ werden kann. Stattdessen wird jede Abbuchung, die das Konto überzieht, abgelehnt. Beide Annahmen müssen sich ändern. Die Kreditlinie beginnt bei 0 und hat im Allgemeinen einen negativen Saldo. Auch wenn ein Kunde zu viel Geld leiht, entsteht eine Gebühr. Die Transaktion wird akzeptiert, es kostet nur mehr. Die erste Regel kann implementiert werden, indem dem Konstruktor ein optionales Argument BankAccount
hinzugefügt wird, das den Mindestsaldo angibt. Der Standardwert lautet 0
. Die zweite Regel erfordert einen Mechanismus, mit dem abgeleitete Klassen den Standardalgorithmus ändern können. Im Grunde genommen fragt die Basisklasse den abgeleiteten Typ, was geschehen soll, wenn es zu einer Überziehung kommt. Das Standardverhalten besteht darin, die Transaktion abzulehnen, indem eine Ausnahme ausgelöst wird.
Beginnen wir mit dem Hinzufügen eines zweiten Konstruktors, der einen optionalen minimumBalance
Parameter enthält. Dieser neue Konstruktor führt alle Aktionen aus, die auch vom vorhandenen Konstruktor ausgeführt werden. Außerdem legt er die Eigenschaft des Mindestsaldos fest. Sie könnten den Text des vorhandenen Konstruktors kopieren, aber das bedeutet, dass Sie in Zukunft an zwei Orten Änderungen vornehmen müssen. Stattdessen können Sie die Konstruktorkette verwenden, um einen anderen Konstruktor aufrufen zu lassen. Der folgende Code zeigt die beiden Konstruktoren und das neue zusätzliche Feld:
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");
}
Der vorangehende Code zeigt zwei neue Techniken. Zuerst wird das Feld minimumBalance
als readonly
gekennzeichnet. Das bedeutet, dass der Wert nach dem Erstellen des Objekts nicht mehr geändert werden kann. Sobald ein Objekt BankAccount
erstellt wurde, kann dies minimumBalance
nicht mehr geändert werden. Zweitens verwendet der Konstruktor, der zwei Parameter annimmt, : this(name, initialBalance, 0) { }
als seine Implementierung. Der : this()
Ausdruck ruft den anderen Konstruktor auf, der einen mit drei Parametern. Mit dieser Technik können Sie eine einzelne Implementierung für die Initialisierung eines Objekts verwenden, obwohl Clientcode einen von vielen Konstruktoren auswählen kann.
Diese Implementierung ruft nur auf MakeDeposit
, wenn der anfängliche Saldo größer als 0
ist. Das behält die Regel bei, dass Einlagen positiv sein müssen, lässt jedoch das Kreditkonto mit einem 0
Saldo eröffnen.
Da die BankAccount
Klasse nun über ein schreibgeschütztes Feld für den Mindestsaldo verfügt, besteht die endgültige Änderung darin, den Hardcode 0
zu minimumBalance
in der MakeWithdrawal
Methode zu ändern.
if (Balance - amount < _minimumBalance)
Nachdem Sie die BankAccount
Klasse erweitert haben, können Sie den LineOfCreditAccount
Konstruktor so ändern, dass er den neuen Basiskonstruktor aufruft, wie im folgenden Code gezeigt:
public LineOfCreditAccount(string name, decimal initialBalance, decimal creditLimit) : base(name, initialBalance, -creditLimit)
{
}
Beachten Sie, dass der LineOfCreditAccount
Konstruktor das Vorzeichen des creditLimit
Parameters ändert, sodass es der Bedeutung des minimumBalance
Parameters entspricht.
Verschiedene Überziehungsregeln
Das letzte hinzuzufügende Merkmal ermöglicht es, eine LineOfCreditAccount
Gebühr für das Überschreiten des Kreditlimits zu berechnen, anstatt die Transaktion zu verweigern.
Eine Technik besteht darin, eine virtuelle Funktion zu definieren, in der Sie das erforderliche Verhalten implementieren. Die BankAccount
Klasse umgestaltet die MakeWithdrawal
Methode in zwei Methoden. Die neue Methode führt die angegebene Aktion aus, wenn die Auszahlung den Saldo unter das Minimum reduziert. Die vorhandene MakeWithdrawal
Methode weist den folgenden Code auf:
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);
}
Ersetzen Sie sie durch den folgenden Code:
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;
}
}
Die hinzugefügte Methode lautet protected
, was bedeutet, dass sie nur von abgeleiteten Klassen aufgerufen werden kann. Diese Deklaration verhindert, dass andere Clients die Methode aufrufen. Es ist auch virtual
so, dass abgeleitete Klassen das Verhalten ändern können. Der Rückgabetyp ist ein Transaction?
. Die ?
Anmerkung gibt an, dass die Methode null
zurückgeben kann. Fügen Sie die folgende Implementierung in die LineOfCreditAccount
ein, um eine Gebühr zu erheben, wenn das Auszahlungslimit überschritten wird.
protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) =>
isOverdrawn
? new Transaction(-20, DateTime.Now, "Apply overdraft fee")
: default;
Die Außerkraftsetzung gibt bei Überziehen des Kontos eine Gebührentransaktion zurück. Wenn der Auszahlungsgrenzwert nicht überschritten wird, gibt die Methode eine null
Transaktion zurück. Das weist darauf hin, dass es keine Gebühr gibt. Testen Sie diese Änderungen, indem Sie der Main
Methode in der Program
Klasse den folgenden Code hinzufügen:
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());
Führen Sie das Programm aus, und überprüfen Sie die Ergebnisse.
Zusammenfassung
Wenn Sie nicht weiterkommen, können Sie die Quelle für dieses Lernprogramm in unserem GitHub-Repository sehen.
In diesem Lernprogramm wurden viele der Techniken gezeigt, die in Object-Oriented Programmierung verwendet werden:
- Sie haben die Abstraktion verwendet, wenn Sie Klassen für jeden der verschiedenen Kontotypen definiert haben. Diese Klassen beschreiben das Verhalten für diesen Kontotyp.
- Sie haben Kapselung eingesetzt, als Sie in jeder Klasse viele Details als
private
festgelegt haben. - Sie haben Vererbung verwendet, als Sie die Implementierung nutzen konnten, die bereits in der
BankAccount
Klasse erstellt wurde, um Code zu sparen. - Sie haben Polymorphismus verwendet, wenn Sie Methoden erstellt haben
virtual
, die abgeleitete Klassen überschreiben könnten, um ein bestimmtes Verhalten für diesen Kontotyp zu erstellen.