Bankensystem
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 106 von 107.
Aufgabe
EinfachLassen Sie uns ein Bankensystem bauen, das Konten, Transaktionen und Finanzoperationen verwaltet! Sie werden ein robustes System erstellen, in dem die Datenintegrität entscheidend ist – Kontostände sollten sich nur über validierte Methoden ändern, und Fehler wie Überziehungen oder ungültige Beträge müssen angemessen behandelt werden.
Sie werden Ihren Code auf fünf Dateien verteilen:
account.go: Definieren Sie einTransactableInterface mit den MethodenDeposit(amount float64) errorundWithdraw(amount float64) error. Erstellen Sie dann zwei Kontotypen, die dieses Interface implementieren:Ein
CheckingAccountmit den nicht exportierten Feldernid,holderundbalance. Girokonten erlauben Abhebungen, die den Kontostand auf genau Null, aber nicht darunter bringen.Ein
SavingsAccountmit denselben nicht exportierten Feldern plus einemminBalanceFeld. Sparkonten müssen ein Mindestguthaben von 100.0 verfügen – Abhebungen, die diesen Schwellenwert unterschreiten würden, sollten fehlschlagen.Beide Kontotypen benötigen Konstruktoren (
NewCheckingAccountundNewSavingsAccount) und eineBalance() float64Getter-Methode. Lehnen Sie bei Einzahlungen negative Beträge oder Null ab. Lehnen Sie bei Abhebungen negative Beträge, Null oder Beträge, die das verfügbare Guthaben übersteigen, ab.transaction.go: Erstellen Sie einTransactionStruct, um Kontoaktivitäten aufzuzeichnen. Jede Transaktion hat einenType(string: "deposit" oder "withdrawal"), einenAmount(float64) und eineAccountID(string). Fügen Sie einenNewTransactionKonstruktor und eineString() stringMethode hinzu, die das Format[Type] $[Amount] on [AccountID]zurückgibt, wobei der Betrag auf zwei Dezimalstellen formatiert ist.bank.go: Bauen Sie einBankStruct, das mehrere Konten verwaltet und Transaktionen aufzeichnet. Speichern Sie Konten in einer Map nach ID und führen Sie eine Slice von Transaktionen. Implementieren Sie:NewBank() *Bank— initialisiert leere SammlungenAddAccount(id string, account Transactable)— registriert ein KontoDeposit(accountID string, amount float64) error— zahlt auf ein Konto ein und zeichnet die Transaktion bei Erfolg aufWithdraw(accountID string, amount float64) error— hebt von einem Konto ab und zeichnet die Transaktion bei Erfolg aufTransfer(fromID, toID string, amount float64) error— verschiebt Geld atomar zwischen Konten (wenn die Abhebung gelingt, aber die Einzahlung fehlschlägt, machen Sie die Abhebung rückgängig)GetBalance(accountID string) (float64, error)— gibt den Kontostand eines Kontos zurück
Geben Sie entsprechende Fehler für nicht existierende Konten zurück.
errors.go: Definieren Sie Sentinel-Errors für Ihre Bankoperationen:ErrAccountNotFound— wenn eine Konto-ID nicht existiertErrInsufficientFunds— wenn die Abhebung das verfügbare Guthaben übersteigtErrInvalidAmount— wenn der Einzahlungs-/Abhebungsbetrag Null oder negativ istErrMinBalanceRequired— wenn eine Sparabhebung das Mindestguthaben verletzen würde
main.go: Erstellen Sie eine Bank und verarbeiten Sie Operationen basierend auf der Eingabe.Lesen Sie die Anzahl der zu erstellenden Konten ein. Lesen Sie für jedes Konto den Kontotyp (
checkingodersavings), die ID, den Inhabernamen und das Anfangsguthaben ein. Erstellen Sie den entsprechenden Kontotyp und fügen Sie ihn der Bank hinzu.Lesen Sie dann die Anzahl der Operationen ein. Jede Operation ist eine von:
deposit [accountID] [amount]withdraw [accountID] [amount]transfer [fromID] [toID] [amount]balance [accountID]
Geben Sie für jede Operation das Ergebnis aus:
OKfür erfolgreiche Einzahlungen/Abhebungen/Transfers, den auf zwei Dezimalstellen formatierten Kontostand für Kontostandsabfragen oderError: [message]für Fehler unter Verwendung Ihrer Sentinel-Errormeldungen.
Die folgenden Eingaben werden bereitgestellt:
- Anzahl der Konten, dann Typ, ID, Inhaber und Anfangsguthaben jedes Kontos (jeweils 4 Zeilen)
- Anzahl der Operationen, dann jede Operation (jeweils 1 Zeile)
Zum Beispiel, gegeben:
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 ACC002Sollte Ihre Ausgabe wie folgt aussehen:
OK
700.00
Error: minimum balance required
Error: insufficient funds
OK
1000.00Und gegeben:
1
checking
C100
Charlie
100.00
4
withdraw C100 100
balance C100
deposit C100 -50
withdraw C999 50Sollte Ihre Ausgabe wie folgt aussehen:
OK
0.00
Error: invalid amount
Error: account not foundBeachten Sie, wie das Transactable Interface es der Bank ermöglicht, mit beiden Kontotypen einheitlich zu arbeiten, während jeder Kontotyp seine eigenen Regeln erzwingt. Die Transfer-Methode demonstriert atomare Operationen – wenn auf halbem Weg 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: Den passenden Kontotyp basierend auf accountType erstellen
// und zur Bank hinzufügen
_ = 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: Jeden Operationstyp behandeln:
// - "deposit": auf das Konto einzahlen, "OK" oder Fehler ausgeben
// - "withdraw": vom Konto abheben, "OK" oder Fehler ausgeben
// - "transfer": zwischen Konten überweisen, "OK" oder Fehler ausgeben
// - "balance": Kontostand abrufen und auf 2 Dezimalstellen formatiert ausgeben
_ = operation
}
}
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool