OOP Capstone Projects

๐Ÿ Python 3.12+ ๐ŸŸข Chapter 35 of 65 ๐Ÿ“‚ Phase 7: Object-Oriented Programming ๐Ÿ“… 2026 Edition
๐Ÿ“Œ Covered in this chapter: 5 Full Projects ยท 1. Bank System ยท 2. Library System ยท 3. Employee System ยท 4. Shopping Cart ยท 5. School System
Build five production-grade Object-Oriented software systems in Python: Bank Account Manager with transactions, Library Catalog with lending, Role-Based Employee Payroll, E-Commerce Shopping Cart with coupon discounts, and School Management System.
1Project 1: Enterprise Bank Management System

An object-oriented banking engine featuring encapsulation, deposit/withdrawal validation, transaction histories, and interest accrual:

๐Ÿ’ป Project 1: Bank Account Management Engine
# =========================================================================
# PROJECT 1: OBJECT-ORIENTED BANK MANAGEMENT SYSTEM
# =========================================================================

class BankAccount:
    interest_rate = 0.04  # 4% Annual Interest (Class Variable)

    def __init__(self, account_number, holder_name, initial_balance=0.0):
        self.account_number = account_number
        self.holder_name = holder_name
        self._balance = float(initial_balance)
        self.transaction_history = [f"Account opened with โ‚น{initial_balance:,.2f}"]

    @property
    def balance(self):
        return self._balance

    def deposit(self, amount):
        if amount <= 0:
            return "โŒ Deposit amount must be positive!"
        self._balance += amount
        self.transaction_history.append(f"Deposited +โ‚น{amount:,.2f}")
        return f"โœ… Deposited โ‚น{amount:,.2f}. New Balance: โ‚น{self._balance:,.2f}"

    def withdraw(self, amount):
        if amount > self._balance:
            return f"โŒ Insufficient funds! Available Balance: โ‚น{self._balance:,.2f}"
        self._balance -= amount
        self.transaction_history.append(f"Withdrew -โ‚น{amount:,.2f}")
        return f"โœ… Withdrew โ‚น{amount:,.2f}. Remaining Balance: โ‚น{self._balance:,.2f}"

    def apply_interest(self):
        interest_earned = self._balance * BankAccount.interest_rate
        self._balance += interest_earned
        self.transaction_history.append(f"Interest credited +โ‚น{interest_earned:,.2f}")
        return f"๐Ÿ“ˆ Interest of โ‚น{interest_earned:,.2f} credited at {BankAccount.interest_rate*100}%"

    def get_statement(self):
        header = f"=== ๐Ÿฆ STATEMENT: {self.holder_name} (Acc #{self.account_number}) ==="
        body = "\n".join([f"  โ€ข {tx}" for tx in self.transaction_history])
        footer = f"Current Available Balance: โ‚น{self._balance:,.2f}\n" + "=" * 50
        return f"{header}\n{body}\n{footer}"

# Run Banking System Demo:
acc = BankAccount("SBI-9021", "Balaji", 10000.0)
print(acc.deposit(5000.0))
print(acc.withdraw(3000.0))
print(acc.apply_interest())
print("\n" + acc.get_statement())
๐Ÿ” OOP Design Principles:
  • Protected _balance with a read-only @property prevents direct unauthorized balance tampering.
  • Full audit logging via self.transaction_history.
2Project 2: Library Catalog & Book Lending Management System

A complete library management system modeling books, borrowers, checkout duration, and book availability:

๐Ÿ’ป Project 2: Library Catalog and Lending System
# =========================================================================
# PROJECT 2: LIBRARY CATALOG & BOOK LENDING SYSTEM
# =========================================================================

class Book:
    def __init__(self, book_id, title, author):
        self.book_id = book_id
        self.title = title
        self.author = author
        self.is_borrowed = False
        self.borrowed_by = None

    def __str__(self):
        status = f"Borrowed by {self.borrowed_by}" if self.is_borrowed else "Available โœ…"
        return f"[{self.book_id}] '{self.title}' by {self.author} ({status})"

class Library:
    def __init__(self, name):
        self.name = name
        self.catalog = {}  # book_id -> Book object

    def add_book(self, book):
        self.catalog[book.book_id] = book
        print(f"๐Ÿ“š Added to library: {book.title}")

    def lend_book(self, book_id, borrower_name):
        book = self.catalog.get(book_id)
        if not book:
            return "โŒ Error: Book not found in catalog!"
        if book.is_borrowed:
            return f"โŒ '{book.title}' is currently borrowed by {book.borrowed_by}!"
        book.is_borrowed = True
        book.borrowed_by = borrower_name
        return f"๐Ÿ“– Successfully issued '{book.title}' to {borrower_name}."

    def return_book(self, book_id):
        book = self.catalog.get(book_id)
        if not book or not book.is_borrowed:
            return "โŒ Error: Book is not currently on loan!"
        borrower = book.borrowed_by
        book.is_borrowed = False
        book.borrowed_by = None
        return f"โœ… '{book.title}' returned successfully by {borrower}."

    def display_catalog(self):
        print(f"\n--- ๐Ÿ›๏ธ {self.name} Catalog ---")
        for b in self.catalog.values():
            print("โ€ข", b)

# Run Library System Demo:
lib = Library("City Central Tech Library")
lib.add_book(Book("B101", "Fluent Python", "Luciano Ramalho"))
lib.add_book(Book("B102", "Clean Code", "Robert C. Martin"))
lib.add_book(Book("B103", "Designing Data-Intensive Apps", "Martin Kleppmann"))

lib.display_catalog()
print("\n" + lib.lend_book("B101", "Balaji"))
print(lib.lend_book("B101", "Alex")) # Test duplicate loan attempt
lib.display_catalog()
print("\n" + lib.return_book("B101"))
๐Ÿ” Composition Architecture:

The Library class manages a collection of independent Book objects, encapsulating borrowing state cleanly.

3Project 3: Role-Based Employee Payroll System (Inheritance)

A polymorphic corporate payroll system with hierarchical roles (Salaried, Hourly, and Commission-based Managers):

๐Ÿ’ป Example 3: Role-Based Employee Payroll Engine
# =========================================================================
# PROJECT 3: ROLE-BASED EMPLOYEE PAYROLL SYSTEM
# =========================================================================
from abc import ABC, abstractmethod

class Employee(ABC):
    def __init__(self, emp_id, name, department):
        self.emp_id = emp_id
        self.name = name
        self.department = department

    @abstractmethod
    def calculate_pay(self):
        """Abstract method calculating monthly compensation."""
        pass

    def __str__(self):
        return f"[{self.emp_id}] {self.name:12} ({self.department:10}) | Monthly Pay: โ‚น{self.calculate_pay():,.2f}"

class SalariedEmployee(Employee):
    def __init__(self, emp_id, name, department, monthly_salary):
        super().__init__(emp_id, name, department)
        self.monthly_salary = monthly_salary

    def calculate_pay(self):
        return self.monthly_salary

class HourlyEmployee(Employee):
    def __init__(self, emp_id, name, department, hourly_rate, hours_worked):
        super().__init__(emp_id, name, department)
        self.hourly_rate = hourly_rate
        self.hours_worked = hours_worked

    def calculate_pay(self):
        return self.hourly_rate * self.hours_worked

class Manager(SalariedEmployee):
    def __init__(self, emp_id, name, department, monthly_salary, bonus):
        super().__init__(emp_id, name, department, monthly_salary)
        self.bonus = bonus

    def calculate_pay(self):
        return self.monthly_salary + self.bonus

# Payroll Processing Engine:
staff = [
    SalariedEmployee("E101", "Balaji", "Engineering", 120000),
    HourlyEmployee("E102", "Alex", "Design", 800, 160),
    Manager("M101", "Chloe", "Management", 150000, 35000)
]

print("--- ๐Ÿ’ผ Monthly Corporate Payroll Summary ---")
total_payroll = 0
for emp in staff:
    print(emp)
    total_payroll += emp.calculate_pay()
print("=" * 60)
print(f"๐Ÿ’ฐ Total Company Payroll Outflow: โ‚น{total_payroll:,.2f}")
๐Ÿ” Polymorphic Payroll:

The payroll loop calls emp.calculate_pay() uniformly without needing to know whether an employee is hourly or a salaried manager.

4Project 4: E-Commerce Shopping Cart System

A complete e-commerce cart architecture with products, quantity management, coupon discounts, and tax computation:

๐Ÿ’ป Project 4: E-Commerce Shopping Cart & Invoice Generator
# =========================================================================
# PROJECT 4: E-COMMERCE SHOPPING CART SYSTEM
# =========================================================================

class Item:
    def __init__(self, item_id, name, unit_price):
        self.item_id = item_id
        self.name = name
        self.unit_price = float(unit_price)

class CartItem:
    def __init__(self, item, quantity=1):
        self.item = item
        self.quantity = quantity

    @property
    def subtotal(self):
        return self.item.unit_price * self.quantity

class ShoppingCart:
    def __init__(self, customer_name):
        self.customer_name = customer_name
        self.items = {}  # item_id -> CartItem

    def add_item(self, item, qty=1):
        if item.item_id in self.items:
            self.items[item.item_id].quantity += qty
        else:
            self.items[item.item_id] = CartItem(item, qty)
        print(f"๐Ÿ›’ Added {qty}x '{item.name}' to cart.")

    def calculate_total(self, discount_percent=0.0, tax_percent=0.18):
        gross = sum(ci.subtotal for ci in self.items.values())
        discount = gross * (discount_percent / 100)
        taxable = gross - discount
        tax = taxable * tax_percent
        final_bill = taxable + tax
        return gross, discount, tax, final_bill

    def print_invoice(self, coupon_discount=10):
        gross, disc, tax, net = self.calculate_total(coupon_discount)
        print("\n" + "=" * 48)
        print(f"๐Ÿ›๏ธ INVOICE: {self.customer_name}'s Cart")
        print("=" * 48)
        for ci in self.items.values():
            print(f"โ€ข {ci.item.name:20} x{ci.quantity} @ โ‚น{ci.item.unit_price:,.2f} = โ‚น{ci.subtotal:,.2f}")
        print("-" * 48)
        print(f"Gross Subtotal:     โ‚น{gross:,.2f}")
        print(f"Coupon ({coupon_discount}% off):  -โ‚น{disc:,.2f}")
        print(f"GST Tax (18%):      +โ‚น{tax:,.2f}")
        print(f"TOTAL PAYABLE:      โ‚น{net:,.2f}")
        print("=" * 48)

# Run Shopping Cart Demo:
laptop = Item("ITM01", "Dell XPS 15", 145000)
mouse = Item("ITM02", "Logitech MX Master", 8500)

cart = ShoppingCart("Balaji")
cart.add_item(laptop, 1)
cart.add_item(mouse, 2)
cart.print_invoice(coupon_discount=10)
๐Ÿ” Domain Separation:

Separates the catalog Item from the dynamic order state CartItem and cart aggregation logic.

5Project 5: School Management System (Students, Teachers, Courses)

A comprehensive school academic administration engine modeling students, faculty teachers, course enrollments, and report card generation:

๐Ÿ’ป Project 5: School & Course Management System
# =========================================================================
# PROJECT 5: SCHOOL MANAGEMENT SYSTEM
# =========================================================================

class Person:
    def __init__(self, person_id, name, email):
        self.person_id = person_id
        self.name = name
        self.email = email

class Teacher(Person):
    def __init__(self, person_id, name, email, subject):
        super().__init__(person_id, name, email)
        self.subject = subject

    def __str__(self):
        return f"๐Ÿ‘จโ€๐Ÿซ Prof. {self.name} (Subject: {self.subject})"

class Course:
    def __init__(self, course_code, title, teacher):
        self.course_code = course_code
        self.title = title
        self.teacher = teacher
        self.enrolled_students = []

    def enroll(self, student):
        if student not in self.enrolled_students:
            self.enrolled_students.append(student)
            student.courses.append(self)

class Student(Person):
    def __init__(self, person_id, name, email, grade_level):
        super().__init__(person_id, name, email)
        self.grade_level = grade_level
        self.courses = []
        self.grades = {}  # course_code -> mark

    def assign_grade(self, course_code, mark):
        self.grades[course_code] = mark

    def get_report_card(self):
        lines = [f"๐ŸŽ“ ACADEMIC REPORT: {self.name} (Grade: {self.grade_level})"]
        for c in self.courses:
            score = self.grades.get(c.course_code, "In Progress")
            lines.append(f"  โ€ข {c.title:25} (Instructor: {c.teacher.name}) -> Score: {score}")
        return "\n".join(lines)

# Run School System Demo:
prof_sharma = Teacher("T01", "Dr. Sharma", "sharma@school.edu", "Python & Algorithms")
prof_rao = Teacher("T02", "Dr. Rao", "rao@school.edu", "Computer Networks")

py_course = Course("CS101", "Advanced Python 3", prof_sharma)
net_course = Course("CS102", "Computer Networks", prof_rao)

student1 = Student("S101", "Balaji", "balaji@school.edu", "12th Standard")
py_course.enroll(student1)
net_course.enroll(student1)

student1.assign_grade("CS101", 96)
student1.assign_grade("CS102", 91)

print(student1.get_report_card())
๐Ÿ” Bidirectional Relationship:

Demonstrates clean relational mapping where Course holds enrolled students and Student maintains enrolled courses.

โš ๏ธ Common Developer Pitfall: Modifying Shared Class Variables Unintentionally in Project Classes

Always ensure instance data (like transaction histories, cart items, or student grade dictionaries) is initialized inside __init__ as an instance variable. Defining cart_items = [] at class level will share the exact same cart across all customers!

๐Ÿ’ป Hands-on Interactive Practice Challenge

Instantiate an account in the BankAccount system, deposit โ‚น5,000, withdraw โ‚น1,200, apply annual interest, and print the generated statement.

Python 3 Practice Challenge โ–ถ Run in Compiler
acc = BankAccount("SBI-5544", "Ravi Kumar", 20000.0)
acc.deposit(8000.0)
acc.withdraw(2500.0)
acc.apply_interest()
print(acc.get_statement())
Run This Challenge in Online Python IDE โ†’
โ“ Frequently Asked Questions (FAQ)

Q Why should I structure enterprise systems with OOP classes instead of raw dictionaries?

Classes provide strict data validation (via properties and type hints), encapsulate business rules alongside state, prevent key misspelling bugs, and allow polymorphic extensions without modifying callers.

Q How do I persist OOP objects to disk?

You can serialize objects to JSON using custom serializers, use the standard library "pickle" module, or map them to relational databases using ORM tools like SQLAlchemy / SQLModel.

Q What is the Single Responsibility Principle (SRP) in OOP design?

SRP states that a class should have only one reason to change (e.g. BankAccount handles balance rules, while StatementPrinter handles formatting and printing).

OC
Written by Our Compiler Technical Editorial Team
Reviewed for accuracy & tested on Python 3.12+ runtime ยท Last updated August 2026