Introduction
1.1 Classdiagram
1.2 Introduction
1.3 Engaging with this document
Abstract methods
2.1 Intro abstract methods
2.2 Abstract class diagram
2.3 Classdiagram
Datahandeling (Adapter Pattern)
3.1 Intro
3.2 Class diagram
3.3 Mock database
3.4 Database handler
3.5 Test block
Loan application (Factory pattern)
4.1 Intro
4.2 Class diagram
4.3 Abstract class
4.4 Loan application algorithm
4.5 Test block
Account Products(Strategy/Factory/Decorator pattern)
5.1 Intro
5.2 Class diagram
5.3 Product Abstract class
5.4 Product algorithms
5.5 Product Factory
5.6 Product Functions
5.7 Test block
Customer Benifits (Strategy Pattern or Abstract Factory)
6.1 Intro
6.2 Class diagram
6.3 Membership Abstract class
6.4 Memberships
6.5 Membership
6.5 Test block
Interfaces (Facade pattern)
7.1 Intro section
7.2 Class diagram
7.3 Interface Loan
7.4 Test Loan Interface
7.5 Interface Accounts
7.6 Test Accounts Interface
7.7 Interface BankMain
7.8 Test BankMain Interface
7.9 Interface Login
This is a full class diagram to realize the full scope of the prototype; the diagram is further broken down in each section for better clarity.
The prototype aims to break down the structures (design patterns) used to ensure usability and flexibility for future extensions. Thus the project uses a combined pattern, combining an abstract factory, strategy, decorator, facade and observer patterns. Each of these patterns offers different benefits to functionalities that this bank requires, thus ensuring the best utilization of the four pillars of programming:
The scope of the prototype encompasses multiple design principles, which will further be broken down in each section, however, one of the most important in the case ABC bank is to "strive for loosely coupled designs between objects that interact". Taking into consideration that ABC bank needs to break into a competitive market with new and innovative solutions, I believe in adopting a modular approach where objects or (clusters for functionality) can plug and played as new needs arise.
Each section has broken up into the different functions of the program, following one or more design patterns, Therefore there will be a short introduction for each describing how they function and why they are chosen.
There is also a class diagram giving a high-level overview of inheritance and composition between relevant objects in that section.
Each section is further subdivided to break down the different functionalities of the objects, there will follow a short explanation for each section if necessary and a test code block, showcasing that module.
At the end of this document, there is a combined interface testing some of the interconnections between each of the modules, this is not a full implementation only a prototype with limited functionality.
Pressing the table of contents sections headers sends you directly to that section, in the same way pressing the button below will shortcut you "Back to table of Contents", allowing you to quickly navigate the prototype and concepts presented.
Since so much of the prototype is based on the use of abstraction and encapsulation it's important to quickly explain how this is done in the python language used. Importing abcmeta will us to use abstract classes and methods, that cannot be called on directly but instead, essentially give us a blueprint allowing us a more modular approach through Object-oriented programming. Thus allowing us to meet the requirements and prepare for future expansion of the functionality.
The code cell below imports the library, to use abstract classes:
#importing ACB meta for using abstract classes
from abc import ABCMeta, abstractmethod
In the diagram below you see that the class teacher has 3 subclasses with an (is-a) relationship essentially meaning that all Professors and instructors are also teachers. "Teacher", in this case, is an abstract class with two abstract methods, these can be polymorphed by any subclasses, to implement their set of rules(privileges) and their naming convention depending on which is instantiated.
ABC bank will most likely implement a relational database instead of NoSQL because of the importance of consistency in transactions of data. Any choice of large-scale databases will need the program's read-and-write requests to be adapted into a query language. The adapter pattern, though ideal for this circumstance, isn't completely showcased in the code block. This is because we needed a functional prototype, so I have instead utilized a python dictionary as key-value storage, for the functionality of the demo. Though the coding differs the principles remain the same, our object(DatabaseHandler) lies in between the program and database(key-value storage) the same way an adapter would, adapting interaction between two objects.
The efficiency of using an adapter means that we can not only extend the object with further adaptation, but we also can replace the object hope with minimal changes To the main code base. Thus incapsulating the necessary functionality used for reading and writing data to the database and allowing the banking system to adhere to design principles such as "classes should open for extension, closed for modification.
The class diagram below is simpler in the sense that has fewer objects.
Below are two simple dictionaries (key-value storage) used for demoing the prototype. Each entry has a key that corresponds to the value. Some keys are even further embedded with other dictionaries.
#customer info including account tied to customer
customerdb = [{"customer_id":1,"name":"Kristofer","surname":"DeYoung","username":"kris","password":"123","member":"gold","accounts":{"savings":"10001","highyeild":"10002","pension":"10003"}},
{"customer_id":2,"name":"Super","surname":"Man","member":"bronze","accounts":{"savings":"10005","highyeild":"10006","current":"10007"}}]
#Transaction database
accountdb = [{"10001":[[500,-100,900]]},{"10002":[[567,-14,500]]},{"10003":[[]]}]
The actual handler has a few universal functions that will be utilized by the rest program, these would need to be further adapted to another data structure or replaced entirely.
The properties in this object are only for demonstration purposes only. Ideally, to maintain the speed and availability of the server, the relevant user data should be loaded into the memory stack once and then used and manipulated from memory, eventually writing back to the database once user operations are concluded. This is possible because most interactions from users on the banking systems will not collide with other users.
class DatabaseHandler():
# reads one user att a time
def __init__(self):
self._monthly_salary = int
self.__account_id = int
self.__time = str
self.__start_date = str
self.__end_date = str
self._age = "age"
# ensure that the next customer id is unique
def check_next_cid(self,database):
temp = 0
for index in database:
if index["customer_id"] > temp:
temp = index["customer_id"]
else:
pass
return temp+1
# searches database and yields one result
def search_db_one(self,database,key,match,result):
for index in database:
if index[key] == match:
return(index[result])
# searches database and yields the relevant dictionary
def search_db_all(self,database,key,match):
for index in database:
if index[key] == match:
return(index)
#retives the whole list with transaction for the specific account
def get_transations(self,match):
for index in accountdb:
for key in index:
if key == match:
return index[key]
# summerizes all values in a list
def get_balance(self,match):
temp = self.get_transations(match)
for row in temp:
return sum(row)
# retrive the last transaction made in an ordered list
def get_last_transation(self,match):
for index in accountdb:
for key in index:
if key == match:
return index[key][0][-1]
# univeral method appending to database
def write_transaction(self,match,value,add):
temp = self.get_transations(match)
for row in temp:
if add == True:
row.append(int(value))
return True
elif add == False:
row.append(int(-value))
return True
else:
return False
else:
return False
Here are some examples of interacting directly with the data handling object, similar to how the program will approach pulling appropriate data when needed.
Interact with the code window below by adding and removing "#" to see individual output:
l1 = DatabaseHandler()
#l1.write_new_db(customerdb)
# search results
print(l1.search_db_one(database=customerdb,key="username",match="kris",result="password"))
print(l1.search_db_one(database=customerdb,key="username",match="kris",result="accounts"))
print(l1.search_db_all(database=customerdb,key="username",match="kris"))
print(l1.check_next_cid(customerdb))
#print(l1.search_db_one(database=accountdb,key="10001",match="kris",result="password"))
print(l1.get_transations("10001"))
print(l1.write_transaction("10001",500,True))
print(l1.get_transations("10001"))
print(l1.get_last_transation("10001"))
print(l1.get_balance("10001"))
123 {'savings': '10001', 'highyeild': '10002', 'pension': '10003'} {'customer_id': 1, 'name': 'Kristofer', 'surname': 'DeYoung', 'username': 'kris', 'password': '123', 'member': 'gold', 'accounts': {'savings': '10001', 'highyeild': '10002', 'pension': '10003'}} 3 [[500, -100, 900]] True [[500, -100, 900, 500]] 500 1800
The loan applications module as an isolated unit follows the Factory pattern. Loan applications are a specific set of sequences that calculate upon commonly inherited properties by parent class, thus avoiding code duplication. The design pattern allows as previously mentioned overwriting the abstract method with the set instruction on how that particular loan application should be processed and what properties will be considered for each type of loan.
Then module eventually returns a yes or no if the loan is approved for the end user, depending on the multiple requirements.
The Different loan applications can easily be expanded upon and offer an extensive range of products such as; refinancing the home loan, boat loan or other types of loans that will need to consider a client's financial situation.
In this case to simplify the reading of this document the rest of this module or the "factory" is found on: 7.3 Interface Loan this allows the user instantiates the chosen object (loan type). Incorporating the design principle "Identify the aspects that vary and separate them from what stays the same."
Blueprint for loan applications, with one method, that allows the subclass to polymorph their own implementation of algorithms. This class initializes properties and the same properties are inherited by all subclass since these don't change despite the object being instantiated.
# All properties are not in use this only for demo purposes
class LoanApplication(metaclass=ABCMeta):
def __init__(self, salary,age):
self._monthly_salary = salary
self.__account_id = int
self.__time = str
self.__start_date = str
self.__end_date = str
self._age = age
@abstractmethod
def loan_approval(self):
pass
The following approval calculations inherit common application properties and essentially overwrite the loan_approval method the parent class. For test purposes only use 2 of the properties.
class HomeLoanApproval(LoanApplication):
def loan_approval(self):
calculate_loan = self._monthly_salary * 12 * 4.5
if self._age >= 50:
print("denied for a loan to old")
return False
elif self._age < 30 and self._age > 18:
print(f"approved for amount {calculate_loan} kr this is a special loan for youth")
return True
elif self._age >= 30 and self._age < 50:
print(f"approved for amount {calculate_loan} kr this is normal loan terms")
return True
else:
print("denied to young or dead")
return False
class LoanApproval(LoanApplication):
def loan_approval(self):
calculate_loan = self._monthly_salary * 2
if self._age >= 50:
print("denied for a loan to old")
return False
elif self._age < 30 and self._age > 18:
print(f"Not approved your to young")
return False
elif self._age >= 30 and self._age < 50:
print(f"approved for amount {calculate_loan} kr this is normal loan terms")
return True
else:
print(f"approved for amount {calculate_loan} kr this is normal loan terms")
return True
class CarLoanApproval(LoanApplication):
def loan_approval(self):
calculate_loan = self._monthly_salary * 12
if self._age >= 50:
print(f"approved for amount {calculate_loan} kr this is normal loan terms")
return True
elif self._age < 30 and self._age > 18:
print(f"approved for amount {calculate_loan} kr, but your a high risk ")
return True
elif self._age >= 30 and self._age < 50:
print(f"approved for amount {calculate_loan} kr this is normal loan terms")
return True
else:
print("denied to young or dead")
return False
Here are some examples of interacting directly with the Loan approval object in a similar fashion to how the program will interact with it.
You can interact with the code window below by adding and removing "#" on one line at a time:
move "#" on one line att a time:
l1 = HomeLoanApproval(50000,22)
l1.loan_approval()
l2 = LoanApproval(50000,65)
l2.loan_approval()
l2._monthly_salary
l3 = CarLoanApproval(50320,18)
l3.loan_approval()
l2._monthly_salary
approved for amount 2700000.0 kr this is a special loan for youth denied for a loan to old denied to young or dead
50000
The Account products module will be the largest in a bank as the transactional behavior of reading and writing a ledger is almost identical whether the product is a type of account as demonstrated below or a mortgage account or credit card account. They will have some form of:
The only variation between accounts is basically that some work on a deficit and some surplus balance, so incorporating them into one module is prudent. Design principle: "Identify the aspects of your application that vary and separate them from what stays the same".
The accounts follow the needs for a classic strategy pattern where they all implement the same modes of operations but with a few different properties supplied for the operations. However, because of the subtle differences in calculations, a combination of Factory and Strategy has been implemented in the code in sections 5.4 - 5.7.
As seen in the diagram below, the credit card and debit card (is-a) CurrentAccount or CreditCardAccount. These decorate underlie accounts transactions, almost qualifying as an interface or facade themselves. Though these need to still be implemented, as many other types of accounts have yet to be implemented, this only goes to show the flexibility and extendability of this module. I would say that the current and planned implementation not only encompasses many of design principles but also SOLID principles:
The abstract class has three abstract methods that allow them to overwrite the instantiated object's properties and sequence of operations.
# the abstract methods are replaced on instanciation
# depending on chosen product
class AccountProducts(metaclass=ABCMeta):
@abstractmethod
def get_balance():
pass
@abstractmethod
def withdraw():
pass
@abstractmethod
def deposit():
pass
Read and write to the correct database according to the object passed further expanding the base will make it simple to copy AccountProducts "blueprint" of the methods available, and add customer products as needed.
class CurrentAccount(AccountProducts):
def __init__(self):
self.__this_account = "Current"
def get_balance(self,amount):
return f"\n{amount} is {self.__this_account} account balance"
def withdraw(self,write):
return f"\nwithdrawing {write} kr from {self.__this_account} account"
def deposit(self,write):
return f"\ndepositing {write} kr {self.__this_account} account"
class HighYeildAccount(AccountProducts):
def __init__(self):
self.__this_account = "Highyeild"
self.__intrest_modifier = 0.50
def get_balance(self,amount):
return f"\n{amount} is {self.__this_account} account balance"
def withdraw(self,write):
return f"\nwithdrawing {write} kr from {self.__this_account} account"
def deposit(self,write):
return f"\ndepositing {write} kr {self.__this_account} account"
class SavingsAccount(AccountProducts):
def __init__(self):
self.__this_account = "Savings"
self.__intrest_modifier = 0.25
def get_balance(self,amount):
return f"\n{amount} is {self.__this_account} account balance"
def withdraw(self,write):
return f"\nwithdrawing {write} kr from {self.__this_account} account"
def deposit(self,write):
return f"\ndepositing {write} kr {self.__this_account} account"
class PensionAccount(AccountProducts):
def __init__(self):
self.__this_account = "Pension"
self.__intrest_modifier = 0.50
self.__locked_date = "date"
def get_locked_date(self):
pass
def get_balance(self,amount):
return f"\n{amount} is {self.__this_account} account balance"
def withdraw(self,write):
return f"\n sense I'm relevant packages to calucate time I'm unsure reached age 65,\
withdrawing {write} kr from {self.__this_account} account"
def deposit(self,write):
return f"\ndepositing {write} kr {self.__this_account} account"
When adding a new Product to the roster, the only place that needs to add code is in the ProductFactory below, adding a new dictionary item such as: '"Homeloan": HomeloanAccount()' and adding a new object with related procures above. An example of a loosely coupled design is closed for modification but open for extension.
# allowing user to input the strategy same as previously and instanciate the relevant object
# returning it for use for another object
class ProductFactory():
__factory_products = ()
def __init__(self):
# Add new products to dictionary
self.__factory_products = {
"current": CurrentAccount(),
"pension": PensionAccount(),
"highyeild": HighYeildAccount(),
"savings": SavingsAccount()
}
# getter and settter property to allow other object to utilize se avalible products
def __get_product(self):
return self.__factory_products
def __set_product(self):
return "\nThis is not allowed"
list_products = property(__get_product,__set_product)
# Instansiates the choosen object
def choosen_product(self,strategy):
while True:
if strategy in self.__factory_products:
return self.__factory_products[strategy]
else:
raise Exception(f"\nProduct: {strategy} in unknown att this bank")
ProductFactory().list_products
{'current': <__main__.CurrentAccount at 0x12ef4fa1610>, 'pension': <__main__.PensionAccount at 0x12ef4fa15e0>, 'highyeild': <__main__.HighYeildAccount at 0x12ef4fa1640>, 'savings': <__main__.SavingsAccount at 0x12ef4fa1730>}
This acts interface to the more complex module, abstracting functionally for ease of use in the rest of the program.
class ProductFuctions():
def __init__(self,product, account_nr):
self.__product_pointer = product
self.__account_id = ""
self.__products_avalible = list
self.__deposit_amount = float
self.__account_number = account_nr
# read the current balance
def get_balance(self):
amount = DatabaseHandler().get_balance(self.__account_number)
temp = ProductFactory().choosen_product(self.__product_pointer).get_balance(amount)
return temp
# reduces the amount in the account
def withdraw(self, withdraw_amount):
# gets balance to check if withdrawal is possible
balance = DatabaseHandler().get_balance(self.__account_number)
if balance >= withdraw_amount:
# instanciates database object to handles transaction, object returns a True if db is updated
amount = DatabaseHandler().write_transaction(self.__account_number,int(withdraw_amount),False)
if amount == True:
temp = ProductFactory().choosen_product(self.__product_pointer).withdraw(withdraw_amount)
# check value against account balance first
return temp
else:
return "transaction failed"
else:
return "not enough to process transaction"
# increases the amount in the account
def deposit(self, deposit_amount):
# instanciates database object to handles transaction, object returns a True if db is updated
amount = DatabaseHandler().write_transaction(self.__account_number,int(deposit_amount),True)
if amount == True:
temp = ProductFactory().choosen_product(self.__product_pointer).deposit(deposit_amount)
# check value against account balance first
return temp
else:
return "transaction failed"
@abstractmethod
def add_product(self):
pass
P1 = ProductFuctions("current", "10001")
print(P1.get_balance())
print(P1.withdraw(5120))
print(P1.get_balance())
print(P1.deposit(100))
print(P1.get_balance())
1800 is Current account balance not enough to process transaction 1800 is Current account balance depositing 100 kr Current account 1900 is Current account balance
The Membership benefits in this implementation are a strategy pattern using polymorphism to dynamically change between the befits packages. However, this will only impact the base rate of interest and be passed on to other aspects of the software, maybe also allowing some limited functions like adding special accounts for gold users.
If the "membership status" is planned to significantly impacts all states of the objects in the future, an implementation with an abstract factory would be better suited for that scenario, thus allowing the instantiated membership to determine the sequence of the rest of the objects.
class MemberShip(metaclass=ABCMeta):
def membership(self):
pass
Simple membership method that passes a weight on interest.
class BronzeMember(MemberShip):
def __init__(self,interest):
self._base_interest = interest
self.intrest_modifier = 0.3
def membership(self):
new_interest = self._base_interest - self.intrest_modifier
return f"\nbronze members get good interest at {new_interest}"
class SilverMember(MemberShip):
def __init__(self,interest):
self._base_interest = interest
self.intrest_modifier = 0.5
def membership(self):
new_interest = self._base_interest - self.intrest_modifier
return f"\nsilver members get better interest at {new_interest}"
class GoldMember(MemberShip):
def __init__(self,interest):
self._base_interest = interest
self.intrest_modifier = 0.7
def membership(self):
new_interest = self._base_interest - self.intrest_modifier
return f"\ngold members get the best interest at {new_interest}"
class CustomerBenfits():
__factory_products = {}
def __init__(self,interest):
self.__interest = interest
# Add new products to dictionary
self.__factory_products = {
"bronze": BronzeMember(self.__interest),
"silver": SilverMember(self.__interest),
"gold": GoldMember(self.__interest)
}
# getter and settter property to allow other object to utilize se avalible products
def __get_product(self):
return self.__factory_products
def __set_product(self):
return "\nThis is not allowed"
list_benfits_products = property(__get_product,__set_product)
# Instansiates the choosen object
def choosen_product(self,strategy):
while True:
if strategy in self.__factory_products:
return self.__factory_products[strategy]
else:
raise Exception(f"\nProduct: {strategy} in unknown att this bank")
M1 = BronzeMember(4).membership()
print(M1)
M2 = SilverMember(4).membership()
print(M2)
M3 = GoldMember(4).membership()
print(M3)
bronze members get good interest at 3.7 silver members get better interest at 3.5 gold members get the best interest at 3.3
This section is split into multiple interfaces that interconnect the loosely coupled objects primarily incorporating the following design principles:
As all modules demonstrated above function individually, the purpose interfaces below are simply interactions between all the elements of the program. Thus incorporating the befits of the Facade pattern essentially masks the complex underlying structure. The interfaces themselves are also loosely coupled in the sense each choice you make will take you to the next object interface, all can be replaced for a desktop system or webpage.
Allows for iteraction with the different loan approvals
class LoanInterface():
# intializer create a list different objects that can instaciated
def __init__(self):
self.__loan_products = {
"homeloan": HomeLoanApproval(50000,22),
"loan": LoanApproval(50000,65),
"Carloan": CarLoanApproval(50320,18)
}
def __get_product(self):
return self.__loan_products
def __set_product(self):
return "This is not allowed"
list_loan_products = property(__get_product,__set_product)
# Instansiates the chosen object
def interact(self):
print("\nWhat kind loan would like to apply for, type the names from below:")
for x in self.list_loan_products:
print(f"Input: {x}")
print("\nInput: back")
choice = input("input ex: homeloan")
if choice == "back":
BankMain().interact()
elif choice in self.__loan_products:
self.__loan_products[choice].loan_approval()
self.interact()
else:
raise Exception(f"Product: {choice} in unknown att this bank")
Remove "#" if you want to try this interface separately from the others.
#L1 = LoanInterface().interact()
#L1.choosen_product("loan")
#L1.list_loan_products
Prints statement and allow user to navigate through menus
class YourAccounts():
def __init__(self):
self.__last_input = int
# pull the current list of account products available
def interact(self):
print("""
Choose which of your accounts you want to interact with by inputting a number:
""")
# retrives accounts tied to login in user
temp = DatabaseHandler().search_db_one(database=customerdb,key="username",match="kris",result="accounts")
temp_keys = list(temp.keys())
product_list = ProductFactory()
for x in product_list.list_products:
for cust_pro in temp_keys:
if x == cust_pro:
print(f"Input: {x}")
else:
pass
print("\nInput: back")
choice = input("\ninput ex: savings ")
temp_account_nr = temp[choice]
if choice == "back":
BankMain().interact()
else:
pass
while True:
if choice in product_list.list_products:
print("\nWhat would like to do, input number:\
\n1 - Deposit\
\n2 - Withdraw\
\n3 - Balance\
\n4 - Transactions\
\n5 - back to accounts")
action = input("input ex: 1")
if action == "1":
print("\nHow much would like Deposit")
amount = int(input("input ex: 2030 "))
b1 = ProductFuctions(choice, temp_account_nr).deposit(amount)
print(b1)
elif action == "2":
print("\nHow much would like Withdraw")
amount = int(input("input ex: 2035 "))
b1 = ProductFuctions(choice, temp_account_nr).withdraw(amount)
print(b1)
elif action == "3":
b1 = ProductFuctions(choice, temp_account_nr).get_balance()
print(b1)
elif action == "4":
b1 = DatabaseHandler().get_transations(temp_account_nr)
print(f"\nthe following transactions have been conducted: {b1}")
elif action == "5":
self.interact()
else:
print("\nTry Again and choose an item from list")
#return product_list.choosen_product(choice)
else:
print(f"\nProduct: {choice} in unknown att this bank, try again")
self.interact()
Remove "#" if you want to try this interface separately from the others.
#y1 = YourAccounts()
#y1.interact()
Allows for iteraction with the different loan approvals
class BankMain():
def __init__(self):
pass
def interact(self):
print("""\n
Choose actions by inputting the number:
1 - Accounts
2 - Loan application
3 - Change customer details
4 - Inbox
5 - logout""")
temp = input("choose action")
if temp == "1":
print("\nAccounts")
YourAccounts().interact()
elif temp == "2":
print("\nLoan application")
LoanInterface().interact()
elif temp == "3":
print("\nChange customer details")
print("\nObs! this functions not available yet, try something else")
self.interact()
elif temp == "4":
print("\nInbox")
print("\nObs! this functions not available yet, try something else")
self.interact()
elif temp == "5":
print("logout")
LoginBank.login()
else:
print("\nPlease input the number of the action you require")
self.interact()
# pull the current list of account products avalible
def get_current_products(self):
product_list = ProductFactory()
for y in product_list.list_products:
print(y)
Remove "#" if you want to try this interface separately from the others.
#B1 = BankMain()
#B1.interact()
class LoginBank():
def __init__(self):
self.__username = "kris"
self.__password = "123"
def login(self):
print("""\n
Choose actions by inputting the number:
1 - login
2 - new customer
3 - end simulation""")
temp = input("choose action")
if temp == "1":
print("you choose to login")
self.check_login_info()
elif temp == "2":
print("\ncreate a customer account")
print("Obs! this option is not available yet, try and use demo account \
username: kris password 123")
self.login()
elif temp == "3":
print("\nyou choose to end program")
else:
print("\nPlease input the number of the action you require")
self.login()
def check_login_info(self):
print("\ninput your username")
username = input("username:")
print("\ninput your password")
password = input("password:")
if username == self.__username and password == self.__password:
user_data = DatabaseHandler().search_db_one(database=customerdb,key="username",match="kris",result="member")
print(CustomerBenfits(3).choosen_product(user_data).membership())
BankMain().interact()
else:
print("\nself.login()something went wrong, try again")
self.login()
The necessity to ensure that the banking system not only functions as per the needs of the current client base but also ensures flexibility to grow and develop as new needs arise or technology matures.
A project of this scale requires a fair amount of planning when combining design patterns and principles that are the foundation of good object-oriented programming. The different parts of the program that are presented, all require different approaches to ensure the best possible user experience. This can therefore cannot be a one size fits all scenario, but applying the fundamentals of OOP will increase the probability of avoiding a complete rework of the system in the future.
Remove "#" if you wish to Run the login interface that runs all the demo parts of the program.
# Main running for the bank "user Interface"
# demo account username: kris and password: 123
#L1 = LoginBank()
#L1.login()