Method Overriding and Polymorphism
Lesson Overview
Method Overriding happens when a child class provides its own version of a method that is already defined in its parent class. This is a key part of Polymorphism (meaning "many forms"), which allows different objects to respond to the same method call in their own unique way. It lets you write flexible code where a single function can handle different types of objects (like a "Car" and a "Boat") without needing to know exactly which one it is
Lesson Content
The Payment Problem: Why We Need Inheritance and Polymorphism
Let's say you're building a billing system for an e-commerce platform. Customers can pay using:
- UPI (instant bank transfer)
- Bank Transfer (manual NEFT/RTGS)
- Credit Card (swipe/tap with OTP)
- Debit Card (PIN-based authorization)
All of them need to accomplish the same goal: deduct money from the customer and update the balance. But the way each payment happens is completely different.Let's explore different ways to implement this in code, from the simplest (but problematic) to the most elegant (using OOP principles).
Approach 1: Writing Separate Functions (The Messy Way)
You might think: "Let me just write four separate functions."
def pay_with_upi(amount):
print("Opening UPI app...")
print("Scanning QR code...")
print(f"₹{amount} deducted from bank account.")
print("Balance updated.")
def pay_with_bank_transfer(amount):
print("Entering bank details...")
print("Verifying IFSC code...")
print(f"₹{amount} transferred via NEFT.")
print("Balance updated.")
def pay_with_credit_card(amount):
print("Swiping card...")
print("Sending OTP...")
print(f"₹{amount} charged to credit account.")
print("Balance updated.")
def pay_with_debit_card(amount):
print("Inserting card...")
print("Enter PIN...")
print(f"₹{amount} deducted from savings account.")
print("Balance updated.") Problems with This Approach:
- Code Duplication: Notice "Balance updated" appears in all four functions. If you need to change how balance updates work (e.g., send an SMS), you have to edit four places. High chance of bugs.
- No Common Interface: Your checkout system has to check: "Is this UPI? Call
pay_with_upi(). Is this a card? Callpay_with_credit_card()..." This becomes a nightmare with 20 payment methods. - Not Scalable: Adding a new payment method (like International Transfer) means writing a brand new function and updating every part of the code that handles payments.
Approach 2: Using Inheritance (The Smart Way)
Instead, let's think like this:
"All payment methods do the same thing at a high level—they PAY and update the balance. But the internal steps are different."
This is where inheritance shines. We create a Parent Class called PaymentMethod that defines the common structure, and then create Child Classes for each specific payment type.
Step 1: The Parent Class (The Blueprint)
class PaymentMethod:
def pay(self, amount):
# This is the "generic" version
print("Processing payment...")
print(f"₹{amount} deducted.")
print("Balance updated.") This parent says: "Every payment method must have a pay() function." But right now, it's just a generic class that can be used by its child classes
Step 2: The Problem—One Size Doesn't Fit All
If we create child classes and don't override the pay() method, they'll all behave exactly the same:
class UPI(PaymentMethod):
pass # No override
class CreditCard(PaymentMethod):
pass # No override
# Both will print "Processing payment..." (generic message)
But we know UPI and Credit Cards work differently. UPI scans a QR code. Credit cards need OTP verification. The parent's generic logic doesn't capture these differences
Method Overriding: Making Each Child Unique
This is where Method Overriding comes in. It's the ability of a child class to replace (override) the parent's method with its own customized version.
Think of it like this:
- The parent says: "Here's a basic recipe for making tea."
- Child 1 (Masala Chai): "I'll follow your recipe, but I'm adding ginger and cardamom."
- Child 2 (Green Tea): "I'll skip the milk entirely and use green tea leaves instead."
Same action (making tea), different implementations
Code with Method Overriding:
class PaymentMethod:
def pay(self, amount):
print("Processing payment...")
print(f"₹{amount} deducted.")
self.update_balance(amount) # Common logic
def update_balance(self, amount):
print("Balance updated in database.")
# Child 1: UPI
class UPI(PaymentMethod):
def pay(self, amount): # OVERRIDING the parent's pay()
print("Opening UPI app...")
print("Scanning QR code...")
print(f"₹{amount} transferred instantly.")
self.update_balance(amount) # Still using parent's balance update
# Child 2: Credit Card
class CreditCard(PaymentMethod):
def pay(self, amount): # OVERRIDING again
print("Swiping card...")
print("Sending OTP to your phone...")
print(f"₹{amount} charged to credit limit.")
self.update_balance(amount)
# Child 3: Debit Card
class DebitCard(PaymentMethod):
def pay(self, amount): # OVERRIDING again
print("Inserting card...")
print("Enter 4-digit PIN...")
print(f"₹{amount} deducted from savings account.")
self.update_balance(amount)
What Just Happened?
- Each child class rewrote the
pay()method to match its own workflow (scanning QR vs. entering PIN). - But they all still inherit the common
update_balance()function from the parent, so we don't repeat that code.
Polymorphism: The Magic Happens
Now comes the beautiful part. Polymorphism is the programming language's ability to let you call the same method name (pay()) on different objects, and automatically execute the right version based on the object type.
The Power of a Unified Interface:
def checkout(payment_method, amount):
# This function doesn't care WHICH payment method it is
# It just knows all payment methods have a pay() function
payment_method.pay(amount)
# Using different payment methods
checkout(UPI(), 500)
print("---")
checkout(CreditCard(), 500)
print("---")
checkout(DebitCard(), 500)
Output:
Opening UPI app...
Scanning QR code...
₹500 transferred instantly.
Balance updated in database.
---
Swiping card...
Sending OTP to your phone...
₹500 charged to credit limit.
Balance updated in database.
---
Inserting card...
Enter 4-digit PIN...
₹500 deducted from savings account.
Balance updated in database.Why This is Powerful:
- Same function (
checkout), works with all payment types. You don't needcheckout_upi(),checkout_card(), etc. - Adding a new payment method is easy. Just create a new child class (e.g.,
InternationalTransfer) with its ownpay()override, andcheckout()works automatically—no changes needed! - The code is clean and maintainable. If you need to change how balance updates work, you edit one place (the parent's
update_balance()), and all children inherit the fix
Summary: The Technical Terms
- Method Overriding: The child class provides its own specific implementation of a method that already exists in the parent. It's a feature you use when writing code
- Polymorphism: The programming language's ability to call the correct overridden method automatically based on the object type. It's the concept or principle that emerges when you use inheritance and overriding together
In simple terms:
- Overriding = You (the programmer) rewriting the method.
- Polymorphism = Python (the language) calling the right version automatically