Menu
Coddy logo textTech

Bankingsystem

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 106 von 107.

challenge icon

Aufgabe

Einfach

Erstellen wir ein Banksystem, das Konten, Transaktionen und Finanzoperationen verwaltet! Du erstellst ein robustes System, bei dem die Datenintegrität entscheidend ist: Kontostände dürfen sich nur durch validierte Methoden ändern, und Fehler wie Überziehungen oder ungültige Beträge müssen angemessen behandelt werden.

Du organisierst deinen Code über fünf Dateien:

  • account.go: Definiere ein Transactable-Interface mit den Methoden Deposit(amount float64) error und Withdraw(amount float64) error. Erstelle anschließend zwei Kontotypen, die dieses Interface implementieren:

    Ein CheckingAccount mit nicht exportierten Feldern id, holder und balance. Girokonten erlauben Abhebungen, die den Kontostand genau auf null bringen, aber nicht darunter.

    Ein SavingsAccount mit denselben nicht exportierten Feldern sowie einem Feld minBalance. Sparkonten müssen einen Mindestkontostand von 100.0 einhalten: Abhebungen, die den Kontostand unter diesen Schwellenwert senken würden, müssen fehlschlagen.

    Beide Kontotypen benötigen Konstruktoren (NewCheckingAccount und NewSavingsAccount) sowie eine Getter-Methode Balance() float64. Bei Einzahlungen müssen negative oder null Beträge abgelehnt werden. Bei Abhebungen müssen negative oder null Beträge sowie Beträge, die die verfügbaren Mittel überschreiten, abgelehnt werden.

  • transaction.go: Erstelle eine Transaction-Struktur, um Kontoaktivitäten aufzuzeichnen. Jede Transaktion besitzt einen Type (string: "deposit" oder "withdrawal"), einen Amount (float64) und eine AccountID (string). Füge einen Konstruktor NewTransaction sowie eine Methode String() string hinzu, die das Format [Type] $[Amount] on [AccountID] zurückgibt, wobei der Betrag mit zwei Dezimalstellen formatiert wird.
  • bank.go: Erstelle eine Bank-Struktur, die mehrere Konten verwaltet und Transaktionen aufzeichnet. Speichere Konten in einer Map nach ID und verwende ein Slice für die Transaktionen. Implementiere:
    • NewBank() *Bank: initialisiert leere Sammlungen
    • AddAccount(id string, account Transactable): registriert ein Konto
    • Deposit(accountID string, amount float64) error: zahlt auf ein Konto ein und zeichnet die Transaktion bei Erfolg auf
    • Withdraw(accountID string, amount float64) error: hebt Geld von einem Konto ab und zeichnet die Transaktion bei Erfolg auf
    • Transfer(fromID, toID string, amount float64) error: überweist Geld atomar zwischen Konten (wenn die Abhebung erfolgreich ist, die Einzahlung jedoch fehlschlägt, wird die Abhebung rückgängig gemacht)
    • GetBalance(accountID string) (float64, error): gibt den Kontostand eines Kontos zurück

    Gib für nicht vorhandene Konten geeignete Fehler zurück.

  • errors.go: Definiere Sentinel-Fehler für deine Bankoperationen:
    • ErrAccountNotFound: wenn eine Konto-ID nicht existiert
    • ErrInsufficientFunds: wenn eine Abhebung den verfügbaren Kontostand überschreitet
    • ErrInvalidAmount: wenn der Einzahlungs- oder Abhebungsbetrag null oder negativ ist
    • ErrMinBalanceRequired: wenn eine Abhebung vom Sparkonto den Mindestkontostand verletzen würde
  • main.go: Erstelle eine Bank und verarbeite Operationen auf Grundlage der Eingabe.

    Lies die Anzahl der zu erstellenden Konten ein. Lies für jedes Konto den Kontotyp (checking oder savings), die ID, den Namen des Inhabers und den Anfangskontostand ein. Erstelle den entsprechenden Kontotyp und füge ihn der Bank hinzu.

    Lies anschließend die Anzahl der Operationen ein. Jede Operation ist eine der folgenden:

    • deposit [accountID] [amount]
    • withdraw [accountID] [amount]
    • transfer [fromID] [toID] [amount]
    • balance [accountID]

    Gib für jede Operation das Ergebnis aus: OK für erfolgreiche Einzahlungen/Abhebungen/Überweisungen, den mit zwei Dezimalstellen formatierten Kontostand für Kontostandabfragen oder Error: [message] für Fehler unter Verwendung deiner Sentinel-Fehlermeldungen.

Die folgenden Eingaben werden bereitgestellt:

  • Anzahl der Konten, anschließend für jedes Konto Typ, ID, Inhaber und Anfangskontostand (jeweils 4 Zeilen)
  • Anzahl der Operationen, anschließend jede Operation (jeweils 1 Zeile)

Beispielsweise bei folgender Eingabe:

2
checking
ACC001
Alice
500.00
savings
ACC002
Bob
1000.00
6
deposit ACC001 200
balance ACC001
withdraw ACC002 950
withdraw ACC001 800
transfer ACC001 ACC002 100
balance ACC002

Deine Ausgabe sollte folgendermaßen aussehen:

OK
700.00
Error: minimum balance required
Error: insufficient funds
OK
1000.00

Und bei folgender Eingabe:

1
checking
C100
Charlie
100.00
4
withdraw C100 100
balance C100
deposit C100 -50
withdraw C999 50

Deine Ausgabe sollte folgendermaßen aussehen:

OK
0.00
Error: invalid amount
Error: account not found

Beachte, wie das Transactable-Interface es der Bank ermöglicht, einheitlich mit beiden Kontotypen zu arbeiten, während jeder Kontotyp seine eigenen Regeln durchsetzt. Die Transfer-Methode demonstriert atomare Operationen: Wenn unterwegs etwas schiefgeht, bleibt das System konsistent!

Probier es selbst

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)

	// Anzahl der Konten lesen
	scanner.Scan()
	numAccounts, _ := strconv.Atoi(scanner.Text())

	// Eine neue Bank erstellen
	bank := NewBank()

	// Konten lesen und erstellen
	for i := 0; i < numAccounts; i++ {
		scanner.Scan()
		accountType := scanner.Text()
		scanner.Scan()
		id := scanner.Text()
		scanner.Scan()
		holder := scanner.Text()
		scanner.Scan()
		balance, _ := strconv.ParseFloat(scanner.Text(), 64)

		// TODO: Erstelle den entsprechenden Kontotyp basierend auf accountType
		// und füge ihn der Bank hinzu
		_ = accountType
		_ = id
		_ = holder
		_ = balance
	}

	// Anzahl der Operationen lesen
	scanner.Scan()
	numOps, _ := strconv.Atoi(scanner.Text())

	// Jede Operation verarbeiten
	for i := 0; i < numOps; i++ {
		scanner.Scan()
		parts := strings.Fields(scanner.Text())
		operation := parts[0]

		// TODO: Behandle jeden Operationstyp:
		// - "deposit": Einzahlung auf Konto, "OK" oder Fehler ausgeben
		// - "withdraw": Abhebung vom Konto, "OK" oder Fehler ausgeben
		// - "transfer": Überweisung zwischen Konten, "OK" oder Fehler ausgeben
		// - "balance": Kontostand abrufen und auf 2 Dezimalstellen formatiert ausgeben
		_ = operation
	}
}

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Go-Compiler