Banking System
Part of the Object Oriented Programming section of Coddy's GDScript journey. Lesson 63 of 65.
Challenge
HardBuild a bank with two kinds of accounts. Every operation returns a result dictionary {"ok": bool, "message": String}; the supplied code prints it as OK: MESSAGE or Failed: MESSAGE. All amounts are float and every amount in a message has two decimals after a $.
Transaction in transaction.gd: the constructor takes kind ("deposit", "withdrawal" or "interest") and amount. _to_string() returns the kind with a capital first letter, then the amount: Deposit - $200.00. A transaction does not store its account.
Account in account.gd is an @abstract class:
- The constructor takes
account_number,owner_nameandinitial_balance. The money lives in the private_balance;balanceis a read-only property whose getter returns_balance._historyis anArray[Transaction], andget_history()returns it. - Two abstract methods:
account_type() -> Stringandwithdrawal_error(amount: float) -> String, which returns why a withdrawal is refused, or"". deposit(amount): an amount of0or less fails withDeposit amount must be positive. Otherwise it adds the money, records adeposittransaction and returnsDeposited $A. New balance: $B.withdraw(amount): an amount of0or less fails withWithdrawal amount must be positive; a non-emptywithdrawal_error()fails with that text. Otherwise it takes the money, records awithdrawaland returnsWithdrew $A. New balance: $B, followed by(Overdraft)when the balance is now below0._to_string()returnsTYPE NUMBER (OWNER): $BALANCE.
SavingsAccount in savings_account.gd extends Account, with the constants INTEREST_RATE = 0.02 and MIN_BALANCE = 100.0. Its type is Savings. A withdrawal that would leave less than MIN_BALANCE is refused with Cannot withdraw below minimum balance of $100.00. apply_interest() adds balance * INTEREST_RATE, records an interest transaction and returns Applied interest: $I. New balance: $B.
CheckingAccount in checking_account.gd extends Account, with the constant OVERDRAFT_LIMIT = 200.0. Its type is Checking. The balance may go down to -200.00; a withdrawal that would go lower is refused with Cannot exceed overdraft limit of $200.00.
Bank in bank.gd keeps the accounts in a private dictionary keyed by account number:
- The constructor takes
bank_name. create_account(kind, account_number, owner_name, initial_balance)is a factory:kindissavingsorcheckingin any letter case. It fails withAccount number already existsorInvalid account type, and otherwise returnsSavings account created successfully(orChecking).get_account(account_number) -> Accountreturns the account, ornull.transfer(from_number, to_number, amount): fails withOne or both accounts not found; withdraws from the first account and, when that fails, fails withTransfer failed:plus the withdrawal's message. Otherwise it deposits into the second account and returnsTransferred $A from FROM to TO.total_holdings() -> floatreturns the sum of all balances.
The supplied code reads the bank name, then commands. For this input:
First Bank
open savings|S1|Alice|1000
open checking|C1|Bob|50
withdraw S1|950
transfer S1|C1|300
withdraw C1|400
history C1
totalthe output is:
OK: Savings account created successfully
OK: Checking account created successfully
Failed: Cannot withdraw below minimum balance of $100.00
OK: Transferred $300.00 from S1 to C1
OK: Withdrew $400.00. New balance: $-50.00 (Overdraft)
History of C1:
1. Deposit - $300.00
2. Withdrawal - $400.00
Total held by First Bank: $650.00Your code goes in transaction.gd, account.gd, savings_account.gd, checking_account.gd and bank.gd. main.gd holds the supplied input/output code and cannot be edited.
Try it yourself
extends Node
func _ready():
# Supplied input/output code: keep it as it is
var bank := Bank.new(OS.read_string_from_stdin())
var line := OS.read_string_from_stdin()
while line != "":
var parts := line.split(" ", true, 1)
var f: PackedStringArray = parts[1].split("|") if parts.size() > 1 else PackedStringArray()
match parts[0]:
"open":
show_result(bank.create_account(f[0], f[1], f[2], float(f[3])))
"deposit":
show_result(bank.get_account(f[0]).deposit(float(f[1])))
"withdraw":
show_result(bank.get_account(f[0]).withdraw(float(f[1])))
"interest":
var account: Account = bank.get_account(f[0])
if account is SavingsAccount:
var savings := account as SavingsAccount
show_result(savings.apply_interest())
else:
print("Failed: ", f[0], " is not a savings account")
"transfer":
show_result(bank.transfer(f[0], f[1], float(f[2])))
"history":
var history: Array = bank.get_account(f[0]).get_history()
print("History of ", f[0], ":")
for i in history.size():
print(i + 1, ". ", history[i])
"account":
print(bank.get_account(f[0]))
"total":
print("Total held by %s: $%.2f" % [bank.bank_name, bank.total_holdings()])
line = OS.read_string_from_stdin()
func show_result(result: Dictionary) -> void:
print(("OK: " if result["ok"] else "Failed: ") + result["message"])
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe self KeywordMethodsPropertiesConstructor (_init)Recap - Simple Calculator4Polymorphism
Method Overriding RevisitedDuck TypingAbstract Classes and MethodsInterface DesignRecap - Shape Calculator2Class Properties
Instance vs Static VariablesStatic FunctionsProperty SettersProperty GettersPrivate by ConventionRecap - Bank Account Manager5Encapsulation
Public and Private MembersValidation with SettersInformation HidingRead-Only PropertiesRecap - Student Records System8Advanced OOP Concepts
Composition vs InheritanceInner ClassesNodes as ComponentsRefCounted vs NodeEnums in Classes11Project: Library Management
Project OverviewBook and User Classes3Inheritance
Basic InheritanceThe super KeywordMethod OverridingInheritance ChainsType Checks with isRecap - Employee Hierarchy6Special Methods
Virtual Methods IntroductionThe _to_string MethodCustom IteratorsRecap - Custom List9Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy PatternPractice on your own: Online GDScript compiler