Bankkonten und Bankinstitut

1. Aufgabe

Erstellen Sie eine Klasse Account, die das Guthaben eines Kontos verwaltet. Zum Anlegen eines Kontos benötigt man (zur Vereinfachung der Aufgabenstellung) nur eine Kontonummer. Die Klasse soll Methoden Deposit zum Einzahlen und Withdrawal zum Abheben besitzen. Auskünfte über das Bankkonto können mit Hilfe der Eigenschaften Number (Kontonummer) und Balance (Kontostand) eingeholt werden. Für Überweisungen von einem Konto auf ein anderes gibt es die Methode Transfer.

Da das Abheben eines Betrags offensichtlich abhängig vom Kontotyp ist, bietet es sich an, in der Klasse Account nur eine abstrakte Vorlage einer Withdrawal-Methode festzulegen. In speziellen Ausprägungen eines Bankkontos wie etwa einem Girokonto oder einem Sparbuch kann man festlegen, inwieweit beispielsweise das Konto beim Abheben überzogen werden darf oder nicht. Durch den Rückgabewert bool teilt die Withdrawal-Methode mit, ob eine Abhebung erfolgreich war oder nicht.

1.1. Girokonten und Sparbücher

Schreiben Sie drei weitere Klassen CurrentAccount (Girokonto), StudentsAccount (Konto ohne Überziehungsrahmen) und DepositAccount (Sparbuch), die Sie geeignet mit der Klasse Account in Beziehung setzen. Die drei Klassen sollen folgende Eigenschaften aufweisen:

  • Klasse CurrentAccount – Ein Girokonto darf um ein Dispolimit überzogen werden, das bei der Kontoeröffnung festzulegen ist. Das Dispolimit ist als Eigenschaft der Klasse CurrentAccount zu realisieren und kann zu einem späteren Zeitpunkt auch angepasst werden. Der Einfachheit halber legen wir zu Grunde, dass für das Überziehen des Girokontos keine Zinsen zu entrichten sind.

  • Klasse StudentsAccount – Ein Konto für Schüler und Studenten verhält sich im Prinzip wie ein Girokonto. Der einzige Unterschied besteht darin, dass es keinen Überziehungsrahmen besitzt.

  • Klasse DepositAccount – Auf einem Sparkonto fallen für das eingezahlte Geld Zinsen an. Der Zinssatz des Sparkontos ist als Eigenschaft der Klasse DepositAccount darzustellen. Nach der Kontoeröffnung kann der Zinssatz nicht mehr geändert werden. Für die Gutschrift der Zinsen auf dem Sparkonto bedarf es einer zusätzlichen Methode ComputeInterest. Diese Methode besitzt als Parameter die Anzahl der Tage, für die die Zinsen gutzuschreiben sind. Die Zinsen sind nach der Formel

    zu berechnen.

1.2. Bankinstitut

Erstellen Sie eine Klasse Bank (Bankinstitut), die eine unbestimmte Anzahl von Bankkonten verwaltet. Das Bankinstitut besitzt Methoden, um ein Sparbuch, ein Girokonto und ein Konto ohne Überziehungsrahmen zu eröffnen. Besitzt ein Kunde ein Konto bei der Bank, kann er mit Hilfe der Kontonummer Einzahlungen, Abhebungen und Überweisungen tätigen. Zusätzlich gewährt das Institut einen Einblick in die Liquidität des Hauses und gibt die Höhe aller Einlagen mit der Eigenschaft TotalBalance preis.

Alle vorgestellten Klassen leiten sich (direkt oder indirekt) von der universellen Basisklasse Object ab. Für textuelle Ausgaben in einer Konsole ist die Methode ToString geeignet zu überschreiben.

2. Lösung

Wir stellen der Reihe nach die geforderten Klassen vor, in Listing 1 beginnen wir mit der Klasse Account:

01: abstract class Account
02: {
03:     // member data
04:     private int number;
05:     protected decimal balance;
06: 
07:     // c'tors
08:     public Account() : this (0) {}
09: 
10:     public Account(int number)
11:     {
12:         this.number = number;
13:         this.balance = 0;
14:     }
15: 
16:     // properties
17:     public int Number
18:     {
19:         get
20:         {
21:             return this.number;
22:         }
23:     }
24: 
25:     public decimal Balance
26:     {
27:         get
28:         {
29:             return this.balance;
30:         }
31:     }
32: 
33:     // public interface
34:     public void Deposit(decimal amount)
35:     {
36:         this.balance += amount;
37:     }
38: 
39:     public abstract bool Withdrawal(decimal amount);
40: 
41:     public bool Transfer(Account target, decimal amount)
42:     {
43:         if (!this.Withdrawal(amount))
44:             return false;
45: 
46:         target.Deposit(amount);
47:         return true;
48:     }
49: 
50:     // overrides
51:     public override String ToString()
52:     {
53:         String s = String.Format("Account No.: {0}", this.number);
54:         s += Environment.NewLine;
55:         String balance = this.balance.ToString("#0.00");
56:         s += String.Format("  Balance = {0}", balance);
57:         return s;
58:     }
59: }

Beispiel 1. Die Klasse Account.


Die Klasse Account kann als Ausgangspunkt für weitere Kontospezialisierungen herangezogen werden, zum Beispiel in Listing 2 für ein Girokonto:

01: class CurrentAccount : Account
02: {
03:     // member data
04:     private decimal limit;
05: 
06:     // c'tors
07:     public CurrentAccount() : this(100) {}
08: 
09:     public CurrentAccount(decimal limit) : base()
10:     {
11:         this.limit = limit;
12:     }
13: 
14:     public CurrentAccount(int number, decimal limit) : base(number)
15:     {
16:         this.limit = limit;
17:     }
18: 
19:     // public interface
20:     public override bool Withdrawal(decimal amount)
21:     {
22:         if (this.balance + this.limit < amount)
23:             return false;
24: 
25:         this.balance -= amount;
26:         return true;
27:     }
28: 
29:     // overrides
30:     public override String ToString()
31:     {
32:         String s = base.ToString();
33:         s += Environment.NewLine;
34:         s += String.Format("  CurrentAccount: Limit = {0}",
35:             this.limit);
36:         s += Environment.NewLine; 
37:         return s;
38:     }
39: }

Beispiel 2. Die Klasse CurrentAccount.


Ein Konto ohne Überziehungsrahmen finden Sie mit der Klasse StudentsAccount in Listing 3 vor:

01: class StudentsAccount : Account
02: {
03:     // c'tors
04:     public StudentsAccount() : base() {}
05:     public StudentsAccount(int number) : base(number) {}
06: 
07:     // public interface
08:     public override bool Withdrawal(decimal amount)
09:     {
10:         if (this.balance < amount)
11:             return false;
12: 
13:         this.balance -= amount;
14:         return true;
15:     }
16: 
17:     // overrides
18:     public override String ToString()
19:     {
20:         String s = base.ToString();
21:         s += Environment.NewLine;
22:         s += String.Format("  StudentsAccount");
23:         s += Environment.NewLine;
24:         return s;
25:     }
26: }

Beispiel 3. Die Klasse StudentsAccount.


Als weiteren Spezialfall für Bankkonten stellen wir mit der Klasse SavingsAccount in Listing 4 noch ein Sparbuch vor:

01: class SavingsAccount : Account
02: {
03:     // member data
04:     private decimal interestRate;
05: 
06:     // c'tors
07:     public SavingsAccount() : this(0, 5) {}
08: 
09:     public SavingsAccount(int number, decimal interestRate) : base(number)
10:     {
11:         this.interestRate = interestRate;
12:     }
13: 
14:     // properties
15:     public decimal InterestRate
16:     {
17:         get
18:         {
19:             return this.interestRate;
20:         }
21:     }
22: 
23:     // public interface
24:     public void ComputeInterest(int numDays)
25:     {
26:         decimal interest =
27:             (numDays * this.interestRate * this.balance) / (365 * 100);
28:         this.balance += interest;
29:     }
30: 
31:     public override bool Withdrawal(decimal amount)
32:     {
33:         if (this.balance < amount)
34:             return false;
35: 
36:         this.balance -= amount;
37:         return true;
38:     }
39: 
40:     // overrides
41:     public override String ToString()
42:     {
43:         String s = base.ToString();
44:         s += Environment.NewLine;
45:         s += String.Format("  SavingsAccount: InterestRate = {0}",
46:             this.interestRate);
47:         s += Environment.NewLine;
48:         return s;
49:     }
50: }

Beispiel 4. Die Klasse SavingsAccount.


Beachten Sie in den Zeilen 3 und 4 von Listing 1 die alternierende Verwendung der Zugriffsklassen private und protected. Natürlich sind alle Instanzvariablen der Basisklasse Account für abgeleitete Klassen bedeutsam. Die unterschiedliche Verwendung der Zugriffsklassen liegt darin begründet, dass in abgeleiteten Klassen Instanzvariablen der Basisklasse in einigen Situationen direkt zugänglich sein müssen, in anderen nicht. Die Instanzvariable balance zum Beispiel wird in den abgeleiteten Klassen dieses Beispiels explizit benötigt, siehe die Methode Withdrawal in den drei abgeleiteten Klassen CurrentAccount (Zeile 20 bis 27 in Listing 2), StudentsAccount (Zeile 8 bis 15 in Listing 3) und SavingsAccount (Zeile 31 bis 38 in Listing 4). Es ergibt bei dieser Instanzvariablen keinen Sinn, sie für den Gebrauch in abgeleiteten Klassen unerreichbar zu deklarieren.

Etwas anders sieht es mit der Kontonummer eines Account-Objekts aus – oder genauer formuliert: Mit der Spezialisierung eines Account-Objekts. Die Kontonummer wird in abgeleiteten Klassen nicht zur direkten Manipulation benötigt (im Gegensatz zum Kontostand, der sich permanent verändern kann). Aus diesem Grund ist die Instanzvariable number mit der Zugriffsklasse private versehen. Außerdem könnte man die Kontonummer eines Account-Objekts jederzeit mit der öffentlichen (read-only) Eigenschaft Number in Erfahrung bringen. Und ebenfalls nicht zu übersehen: Indirekt kann die Instanzvariable number sehr wohl angesprochen werden, wie dies etwa beim Aufruf eines Basisklassenkonstruktors der Fall ist (siehe zum Beispiel den Aufruf von base in Zeile 9 von Listing 2). Solange geerbte Instanzvariablen nicht direkt für einen dauerhaften Zugriff erforderlich sind, ist ein Einsatz der restriktiveren Zugriffsklasse private gegenüber protected vorzuziehen.

Ein weiterer Hinweis bezieht sich auf die unterschiedlichen Signaturen der beiden Methoden Deposit und Withdrawal in den Zeilen 34 bzw. 39 von Listing 1 der Klasse Account. Für das Einzahlen eines Geldbetrags ist kein Grund erkennbar, dass dieses Verhalten in einer abgeleiteten Klasse anders aussieht. Für die Deposit-Methode wird deshalb das Überschreiben nicht angeboten, sie wird ohne das Schlüsselwort virtual (oder abstract) gleich in der abstrakten Basisklasse implementiert. Anders verhält es sich natürlich, wenn Geld von einem Konto abgehoben wird. Hier spielen Aspekte wie ein Überziehungskredit, Anspruch auf Zinsen usw. eine Rolle, die Withdrawal-Methode ist daher mit dem Schlüsselwort abstract gekennzeichnet.

Eine weitere Finesse finden Sie in der Implementierung der Methode Transfer der abstrakten Basisklasse Account vor (Zeilen 41 bis 48 von Listing 1). Die Methode ist weder mit virtual noch mit abstract markiert, sondern einfach komplett implementiert. Dies stellt auch kein grundsätzliches Problem dar, da man zum Überweisen eines Geldbetrags nur die zwei Methoden Withdrawal und Deposit benötigt. In der Realisierung von Transfer wird jedoch eine Methode Withdrawal aufgerufen (Zeile 43), die in der Klasse Account überhaupt keine Implementierung besitzt, da sie abstrakt definiert ist. Dies ist sehr wohl möglich wie auch in diesem Beispiel sinnvoll, denn: Die Klasse Account ist zunächst einmal abstrakt (sprich unvollständig) definiert. Von ihr können also keine Objekte angelegt werden. Damit kann auch kein Aufruf von Transfer mit einer fehlenden Methode Withdrawal vorgenommen werden. In konkreten abgeleiteten Klassen verhält es sich anders: Hier muss (im Falle einer konkreten Ableitung) eine Realisierung von Withdrawal vorliegen. Damit lässt sich an Objekten des abgeleiteten Klassentyps die geerbte Methoden Transfer aufrufen, in ihrer Implementierung resultiert die Anweisung

if (!this.Withdrawal(amount))
    return false;

im Aufruf der überschriebenen Withdrawal-Methode in der abgeleiteten Klasse! Alles verstanden? Wenn nicht, dann könnte Ihnen vielleicht die zeilenweise Ausführung eines Beispielprogramms mit dem Debugger weiterhelfen. Dieses Szenario ist eine Anwendung des virtuellen Methodenaufrufmechanismus in Reinkultur!

Beispiel::

CurrentAccount a1 = new CurrentAccount(1, 100);
a1.Deposit(50);
Console.WriteLine(a1);

SavingsAccount a2 = new SavingsAccount(2, 5);
a2.Deposit(100);
a2.ComputeInterest(180);
Console.WriteLine(a2);

StudentsAccount a3 = new StudentsAccount(3);
a3.Deposit(50);
a3.Withdrawal(100);
Console.WriteLine(a3);

a1.Transfer(a2, 60);
Console.WriteLine(a1);
Console.WriteLine(a2);

Ausführung::

Account No.: 1
  Balance = 50,00
  CurrentAccount: Limit = 100

Account No.: 2
  Balance = 102,47
  SavingsAccount: InterestRate = 5

Account No.: 3
  Balance = 50,00
  StudentsAccount

Account No.: 1
  Balance = -10,00
  CurrentAccount: Limit = 100

Account No.: 2
  Balance = 162,47
  SavingsAccount: InterestRate = 5

Damit kommen wir in Listing 5 auf das Bankinstitut zu sprechen. Auf Grund der Konzeption unterschiedlicher Bankkonten als Spezialisierung einer Basisklasse Account lassen sich alle Konten in einem Array des Typs Account[] verwalten. Die drei Methoden CreateCurrentAccount, CreateSavingsAccount und CreateStudentsAccount legen jeweils ein Objekt von einer konkreten Spezialisierung der abstrakten Klasse Account an.

Die Methoden Deposit und WithDrawal zum Einzahlen und Abheben besitzen (neben dem Geldbetrag) eine Kontonummer als Parameter. Mit ihre Hilfe ist im Account[]-Array das korrespondierenden Account-Objekt zu lokalisieren und die fragliche Kontooperation durchzuführen.

001: class Bank
002: {
003:     // member data
004:     private int nextNumber;     // next available account number
005:     private Account[] accounts; // array of accounts
006:     private String name;
007: 
008:     // c'tors
009:     public Bank() : this ("") {}
010: 
011:     public Bank(String name)
012:     {
013:         this.nextNumber = 100000;
014:         this.accounts = new Account[0];
015:         this.name = name;
016:     }
017: 
018:     // properties
019:     public decimal TotalBalance
020:     {
021:         get
022:         {
023:             decimal total = 0;
024:             for (int i = 0; i < this.accounts.Length; i ++)
025:                 total += this.accounts[i].Balance;
026:             return total;
027:         }
028:     }
029: 
030:     // public interface
031:     public int CreateCurrentAccount(decimal limit)
032:     {
033:         // create new account
034:         this.nextNumber++;
035:         CurrentAccount a = new CurrentAccount(this.nextNumber, limit);
036: 
037:         // append new account
038:         this.AppendAccount(a);
039: 
040:         // return account number
041:         return a.Number;
042:     }
043: 
044:     public int CreateSavingsAccount(decimal interestRate)
045:     {
046:         // create new account
047:         this.nextNumber++;
048:         SavingsAccount a = new SavingsAccount(this.nextNumber, interestRate);
049: 
050:         // append new account
051:         this.AppendAccount(a);
052: 
053:         // return account number
054:         return a.Number;
055:     }
056: 
057:     public int CreateStudentsAccount()
058:     {
059:         // create new account
060:         this.nextNumber++;
061:         StudentsAccount a = new StudentsAccount(this.nextNumber);
062: 
063:         // append new account
064:         this.AppendAccount(a);
065: 
066:         // return account number
067:         return a.Number;
068:     }
069: 
070:     public bool Deposit(int number, decimal amount)
071:     {
072:         // search account
073:         for (int i = 0; i < this.accounts.Length; i++)
074:         {
075:             if (this.accounts[i].Number == number)
076:             {
077:                 // credit account
078:                 this.accounts[i].Deposit(amount);
079:                 return true;
080:             }
081:         }
082: 
083:         return false;
084:     }
085: 
086:     public bool WithDrawal(int number, decimal amount)
087:     {
088:         // search account
089:         for (int i = 0; i < this.accounts.Length; i++)
090:         {
091:             if (this.accounts[i].Number == number)
092:             {
093:                 // charge account
094:                 return this.accounts[i].Withdrawal(amount);
095:             }
096:         }
097: 
098:         return false;
099:     }
100: 
101:     public bool Transfer(int source, int target, decimal amount)
102:     {
103:         // search account to charge
104:         Account chargeAccount = null;
105:         for (int i = 0; i < this.accounts.Length; i++)
106:         {
107:             if (this.accounts[i].Number == source)
108:             {
109:                 chargeAccount = this.accounts[i];
110:                 break;
111:             }
112:         }
113:         if (chargeAccount == null)
114:             return false;
115: 
116:         // search account to credit
117:         Account creditAccount = null;
118:         for (int i = 0; i < this.accounts.Length; i++)
119:         {
120:             if (this.accounts[i].Number == source)
121:             {
122:                 creditAccount = this.accounts[i];
123:                 break;
124:             }
125:         }
126:         if (creditAccount == null)
127:             return false;
128: 
129:         return chargeAccount.Transfer(creditAccount, amount);
130:     }
131: 
132:     // private helper methods
133:     private void AppendAccount(Account a)
134:     {
135:         // allocate new account's array
136:         Account[] tmp = new Account[this.accounts.Length + 1];
137:         for (int i = 0; i < this.accounts.Length; i++)
138:             tmp[i] = this.accounts[i];
139: 
140:         // append new account
141:         tmp[this.accounts.Length] = a;
142: 
143:         // switch buffer
144:         this.accounts = tmp;
145:     }
146: 
147:     // overrides
148:     public override String ToString()
149:     {
150:         String s = String.Format("Bank: {0}", this.name);
151:         s += Environment.NewLine;
152: 
153:         for (int i = 0; i < this.accounts.Length; i++)
154:         {
155:             s += this.accounts[i].ToString();
156:             s += Environment.NewLine;
157:         }
158: 
159:         return s;
160:     }
161: }

Beispiel 5. Die Klasse Bank.


Beispiel:

Bank bank = new Bank("Sparkasse");

int currentAccountNum = bank.CreateCurrentAccount(1000);
bank.Deposit(currentAccountNum, 500);
bank.WithDrawal(currentAccountNum, 300);
Console.WriteLine("Current Balance: {0}", bank.TotalBalance);

int savingsAccountNum = bank.CreateSavingsAccount(4);
bank.Deposit(savingsAccountNum, 100);
bank.Deposit(savingsAccountNum, 50);
Console.WriteLine("Current Balance: {0}", bank.TotalBalance);

int studentsAccountNum = bank.CreateSavingsAccount(4);
bank.Deposit(studentsAccountNum, 20);
Console.WriteLine("Current Balance: {0}", bank.TotalBalance);
Console.WriteLine();

Console.WriteLine(bank);

bank.Transfer(currentAccountNum, savingsAccountNum, 150);
Console.WriteLine(bank);

Ausführung:

Current Balance: 200
Current Balance: 350
Current Balance: 370

Bank: Sparkasse
Account No.: 100001
  Balance = 200,00
  CurrentAccount: Limit = 1000

Account No.: 100002
  Balance = 150,00
  SavingsAccount: InterestRate = 4

Account No.: 100003
  Balance = 20,00
  SavingsAccount: InterestRate = 4


Bank: Sparkasse
Account No.: 100001
  Balance = 200,00
  CurrentAccount: Limit = 1000

Account No.: 100002
  Balance = 150,00
  SavingsAccount: InterestRate = 4

Account No.: 100003
  Balance = 20,00
  SavingsAccount: InterestRate = 4