Practical Projects and Exercises Combining Concepts

Lesson Overview

This lesson ties everything together with a practical project: a Banking System. You will build a complete system from scratch using classes, objects, encapsulation, inheritance, and abstraction. This project simulates a real-world library where you can add books, register members, lend books, and handle returns, demonstrating how OOP principles work together in a real application.

Lesson Content

OOP Banking System: From Basics to Professional Application

Project Overview

In this comprehensive project, you'll build a professional Banking System using all the Object-Oriented Programming concepts you've learned. This isn't just theory—this is how real banking applications are structured in production systems.​

By the end of this lesson, you'll have created a fully functional banking system that demonstrates:

  • Abstract Base Classes and Interfaces
  • Inheritance and Method Overriding
  • Encapsulation with Private Variables
  • Getter and Setter Methods with Validation
  • Instance, Class, and Static Methods
  • Polymorphism in Action

Why This Project Matters

Banks handle millions of transactions daily. Their systems must be secure, consistent, and maintainable. OOP provides the architectural foundation that makes this possible​

Real-World Connection:

  • All Banks use OOP principles in their core banking systems
  • Payment integrators rely on abstract interfaces to integrate multiple payment methods
  • ATM systems use encapsulation to protect your account data
The Challenge: Managing Multiple Account Types

Imagine you're the CTO of a new bank. You need to support three different account types:

  • Savings Account: Has minimum balance requirements and earns interest

  • Current Account: Designed for businesses, allows overdrafts, no interest

Each account type has different rules but shares common features like deposits, withdrawals, and balance updates. How do you organize this code so that:

  1. 50 developers can work on different account types without breaking each other's code

  2. New account types (like Credit Cards or Loans) can be added easily

  3. Core banking operations remain consistent across all accounts

  4. Sensitive data like balance and PIN are protected from unauthorized access

This is where OOP principles become your architectural foundation.

Step 1: The Solution: Abstract Base Classes

Solution: Create an Abstract Base Class that says: "Every account type MUST have these exact methods, or Python won't let you create objects."

The Architecture at a Glance

BankAccount (Abstract Base Class)
├── Private Variables: __balance, __pin, __account_number                # Private variables that adds security, need to use setter and getter methods to access or update data
├── Public Variables: account_holder_name, account_creation_date         # Public Variables
├── Abstract Methods: calculate_interest(), withdraw()                   # Methods that are must needeed when implementing a Child Classes, these methods cannot be ingnored 
├── Concrete Methods: deposit(), get_balance(), set_pin()                # Instance Methods that add functionlity/featrues
├── Class Methods: get_total_accounts(), get_bank_name()                 # Methods that are based on Class, but not based on Instance   
└── Static Methods: calculate_tds()                                      # Standalone method that used to do some calcualtions.   

Code Implementation:

from abc import ABC, abstractmethod
from datetime import datetime
import random

# Abstract Base Class - The Contract
class BankAccount(ABC):
    """
    Blueprint for all bank accounts.
    Every account type must implement these methods.
    """
    
    # Class variable - shared across all accounts
    total_accounts = 0
    bank_name = "ByLearning Bank of India"
    
    def __init__(self, account_holder_name, initial_deposit):
        # Private variables (encapsulation)
        self.__account_number = self._generate_account_number()            # Calls a private method to generate account number
        self.__balance = 0
        self.__pin = None
        
        # Public variables
        self.account_holder_name = account_holder_name
        self.account_creation_date = datetime.now().strftime("%Y-%m-%d")
        
        # Increment class variable
        BankAccount.total_accounts += 1
        
        # Initial deposit
        self.deposit(initial_deposit)                                    # calls a method to deposit money
    
    # Private method (helper method)
    def _generate_account_number(self):
        """Generates unique 10-digit account number"""
        return random.randint(1000000000, 9999999999)
    
    # Abstract methods - MUST be implemented by child classes
    @abstractmethod
    def calculate_interest(self):
        """Each account type calculates interest differently"""
        pass
    
    @abstractmethod
    def withdraw(self, amount):
        """Each account type has different withdrawal rules"""
        pass
    
    # Concrete methods - inherited by all child classes
    def deposit(self, amount):
        """Common deposit logic for all accounts"""
        if amount <= 0:
            print("Deposit amount must be positive.")
            return False
        
        self.__balance += amount
        print(f"₹{amount} deposited successfully.")
        return True
    
    # Getter methods
    def get_balance(self):
        """Safe way to view balance"""
        return self.__balance
    
    def get_account_number(self):
        """Safe way to view account number"""
        return self.__account_number
    
    # Setter method with validation
    def set_pin(self, new_pin):
        """Set a 4-digit PIN"""
        if len(str(new_pin)) != 4 or not str(new_pin).isdigit():
            print("PIN must be exactly 4 digits.")
            return False
        self.__pin = new_pin
        print("PIN set successfully.")
        return True
    
    def verify_pin(self, pin):
        """Verify PIN without exposing it"""
        return self.__pin == pin
    
    # Class method - works on the class, not instances
    @classmethod
    def get_total_accounts(cls):
        """Returns total accounts created across all types"""
        return cls.total_accounts
    
    @classmethod
    def get_bank_name(cls):
        """Returns the bank's name"""
        return cls.bank_name
    
    # Static methods - utility functions
    @staticmethod
    def calculate_tds(interest_amount):
        """Calculates 10% TDS on interest earned"""
        return interest_amount * 0.10

Step 2: Savings Account - Inheritance & Method Overriding

Real-World Rules:

  • Minimum balance: ₹1,000
  • Interest rate: 4% per annum
  • Daily withdrawal limit: ₹50,000

Code Implementation:

class SavingsAccount(BankAccount):
    """
    Savings account with minimum balance requirement
    """
    
    # Class variables specific to savings accounts
    minimum_balance = 1000
    interest_rate = 0.04  # 4%
    daily_withdrawal_limit = 50000
    
    def __init__(self, account_holder_name, initial_deposit):
        if initial_deposit < self.minimum_balance:
            raise ValueError(f"Initial deposit must be at least ₹{self.minimum_balance}")
        
        super().__init__(account_holder_name, initial_deposit)                    # Initating the exiting variables, methods on Abstract class
        self.account_type = "Bylearning Savings Account"
        self.__daily_withdrawn_today = 0
    
    # Overriding abstract method
    def calculate_interest(self):
        """Calculate 4% interest on current balance"""
        balance = self.get_balance()
        interest = balance * self.interest_rate
        
        # Deduct TDS
        tds = BankAccount.calculate_tds(interest)
        net_interest = interest - tds
        
        print(f"Interest Earned: ₹{interest:.2f}")
        print(f"TDS Deducted (10%): ₹{tds:.2f}")
        print(f"Net Interest Credited: ₹{net_interest:.2f}")
        
        self.deposit(net_interest)
        return net_interest
    
    # Overriding abstract method
    def withdraw(self, amount, pin):
        """Withdraw with minimum balance and daily limit checks"""
        
        # PIN verification
        if not self.verify_pin(pin):
            print("Incorrect PIN.")
            return False
        
        # Validation checks
        if amount <= 0:
            print("Withdrawal amount must be positive.")
            return False
        
        balance = self.get_balance()
        
        # Check minimum balance
        if balance - amount < self.minimum_balance:
            print(f"Cannot withdraw. Minimum balance of ₹{self.minimum_balance} must be maintained.")
            return False
        
        # Check daily limit
        if self.__daily_withdrawn_today + amount > self.daily_withdrawal_limit:
            print(f"Daily withdrawal limit of ₹{self.daily_withdrawal_limit} exceeded.")
            return False
        
        # Process withdrawal
        self._BankAccount__balance -= amount
        self.__daily_withdrawn_today += amount
        
        
        print(f"₹{amount} withdrawn successfully.")
        print(f"Daily withdrawal used: ₹{self.__daily_withdrawn_today}/{self.daily_withdrawal_limit}")
        return True
Tags: python

💬 Comments (0)

No comments yet. Be the first to share your thoughts!