Module 11 - OOP Advanced (Inheritance, Polymorphism, Encapsulation)
This module covers advanced OOP concepts including inheritance, polymorphism, encapsulation, multiple inheritance, and method resolution order (MRO).
1. Inheritance
1.1 Basic Inheritance
# Base (parent) class
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Some sound"
# Derived (child) class
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Buddy says Woof!
print(cat.speak()) # Whiskers says Meow!
1.2 super() Function
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def info(self):
return f"{self.brand} {self.model}"
class Car(Vehicle):
def __init__(self, brand, model, doors):
super().__init__(brand, model) # Call parent constructor
self.doors = doors
def info(self):
base_info = super().info() # Call parent method
return f"{base_info} with {self.doors} doors"
car = Car("Toyota", "Camry", 4)
print(car.info()) # Toyota Camry with 4 doors
1.3 Method Overriding
class Shape:
def area(self):
return 0
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self): # Override parent method
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self): # Override parent method
return 3.14159 * self.radius ** 2
shapes = [Rectangle(5, 3), Circle(4)]
for shape in shapes:
print(f"Area: {shape.area()}")
Inheritance Benefits
- Code reusability
- Logical hierarchy
- Extensibility
- Polymorphism support
2. Multiple Inheritance
2.1 Basic Multiple Inheritance
class Flyable:
def fly(self):
return "Flying"
class Swimmable:
def swim(self):
return "Swimming"
class Duck(Flyable, Swimmable):
def quack(self):
return "Quack!"
duck = Duck()
print(duck.fly()) # Flying
print(duck.swim()) # Swimming
print(duck.quack()) # Quack!
2.2 Diamond Problem and MRO
class A:
def method(self):
return "A"
class B(A):
def method(self):
return "B"
class C(A):
def method(self):
return "C"
class D(B, C):
pass
d = D()
print(d.method()) # B (follows MRO)
print(D.__mro__) # Method Resolution Order
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
2.3 Mixins
class JSONMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)
class LoggingMixin:
def log(self, message):
print(f"[{self.__class__.__name__}] {message}")
class User(JSONMixin, LoggingMixin):
def __init__(self, name, email):
self.name = name
self.email = email
user = User("Alice", "alice@example.com")
print(user.to_json()) # {"name": "Alice", "email": "alice@example.com"}
user.log("User created") # [User] User created
3. Polymorphism
3.1 Method Polymorphism
class Payment:
def process(self, amount):
raise NotImplementedError("Subclass must implement")
class CreditCard(Payment):
def process(self, amount):
return f"Processing ${amount} via Credit Card"
class PayPal(Payment):
def process(self, amount):
return f"Processing ${amount} via PayPal"
class Bitcoin(Payment):
def process(self, amount):
return f"Processing ${amount} via Bitcoin"
def make_payment(payment_method, amount):
print(payment_method.process(amount))
# Polymorphism in action
make_payment(CreditCard(), 100)
make_payment(PayPal(), 50)
make_payment(Bitcoin(), 200)
3.2 Duck Typing
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class Robot:
def speak(self):
return "Beep boop!"
def animal_sound(animal):
# Duck typing: If it has speak(), call it
print(animal.speak())
animal_sound(Dog()) # Woof!
animal_sound(Cat()) # Meow!
animal_sound(Robot()) # Beep boop!
4. Abstract Base Classes
4.1 Creating Abstract Classes
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
# shape = Shape() # TypeError: Can't instantiate abstract class
rect = Rectangle(5, 3)
print(rect.area()) # 15
print(rect.perimeter()) # 16
4.2 Abstract Properties
from abc import ABC, abstractmethod
class Vehicle(ABC):
@property
@abstractmethod
def max_speed(self):
pass
@abstractmethod
def start_engine(self):
pass
class Car(Vehicle):
@property
def max_speed(self):
return 200
def start_engine(self):
return "Engine started!"
car = Car()
print(car.max_speed) # 200
print(car.start_engine()) # Engine started!
5. Encapsulation
5.1 Access Modifiers
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # Public
self._balance = balance # Protected
self.__pin = "1234" # Private
def deposit(self, amount):
if amount > 0:
self._balance += amount
def __verify_pin(self, pin): # Private method
return pin == self.__pin
def withdraw(self, amount, pin):
if self.__verify_pin(pin) and amount <= self._balance:
self._balance -= amount
return True
return False
@property
def balance(self):
return self._balance
account = BankAccount("Alice", 1000)
account.deposit(500)
print(account.balance) # 1500
account.withdraw(200, "1234")
print(account.balance) # 1300
5.2 Property-based Encapsulation
class Employee:
def __init__(self, name, salary):
self._name = name
self._salary = salary
@property
def name(self):
return self._name
@property
def salary(self):
return self._salary
@salary.setter
def salary(self, value):
if value < 0:
raise ValueError("Salary cannot be negative")
if value < self._salary:
print("Warning: Salary decreased")
self._salary = value
@property
def annual_salary(self):
return self._salary * 12
emp = Employee("Alice", 5000)
print(emp.salary) # 5000
emp.salary = 5500 # Update
print(emp.annual_salary) # 66000
6. Composition vs Inheritance
6.1 Composition
class Engine:
def start(self):
return "Engine started"
def stop(self):
return "Engine stopped"
class Car:
def __init__(self, brand):
self.brand = brand
self.engine = Engine() # Composition
def start(self):
return f"{self.brand}: {self.engine.start()}"
def stop(self):
return f"{self.brand}: {self.engine.stop()}"
car = Car("Toyota")
print(car.start()) # Toyota: Engine started
6.2 When to Use Each
| Use Inheritance When | Use Composition When |
|---|---|
| "is-a" relationship | "has-a" relationship |
| Strong coupling desired | Flexibility needed |
| Behavior shared | Behavior delegated |
| Example: Dog is-a Animal | Example: Car has-a Engine |
7. Real-World Example
from abc import ABC, abstractmethod
from datetime import datetime
class Employee(ABC):
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
@abstractmethod
def calculate_salary(self):
pass
def display_info(self):
return f"{self.name} (ID: {self.employee_id})"
class FullTimeEmployee(Employee):
def __init__(self, name, employee_id, monthly_salary):
super().__init__(name, employee_id)
self.monthly_salary = monthly_salary
def calculate_salary(self):
return self.monthly_salary
class HourlyEmployee(Employee):
def __init__(self, name, employee_id, hourly_rate, hours_worked):
super().__init__(name, employee_id)
self.hourly_rate = hourly_rate
self.hours_worked = hours_worked
def calculate_salary(self):
return self.hourly_rate * self.hours_worked
class Payroll:
def __init__(self):
self.employees = []
def add_employee(self, employee):
self.employees.append(employee)
def process_payroll(self):
print("Processing Payroll...")
total = 0
for emp in self.employees:
salary = emp.calculate_salary()
total += salary
print(f"{emp.display_info()}: ${salary:.2f}")
print(f"Total Payroll: ${total:.2f}")
# Usage
payroll = Payroll()
payroll.add_employee(FullTimeEmployee("Alice", "E001", 5000))
payroll.add_employee(HourlyEmployee("Bob", "E002", 25, 160))
payroll.process_payroll()
8. Summary
| Concept | Description | Example |
|---|---|---|
| Inheritance | Create class from another | class Dog(Animal): |
| super() | Call parent method | super().__init__() |
| Multiple inheritance | Inherit from multiple classes | class D(B, C): |
| Polymorphism | Same interface, different implementation | shape.area() |
| Abstract class | Cannot be instantiated | class Shape(ABC): |
| Encapsulation | Hide internal details | self.__private |
| Composition | Has-a relationship | self.engine = Engine() |
Key Takeaways
- Use inheritance for "is-a" relationships
- Favor composition over inheritance when possible
- Use abstract classes to define interfaces
- Implement polymorphism for flexibility
- Encapsulate internal details
- Understand MRO for multiple inheritance
9. What's Next?
In Module 12 - Special Methods, you'll learn:
- Dunder methods (
__add__,__len__, etc.) - Operator overloading
- Context managers
- Callable objects
- Custom containers
10. Practice Exercises
Exercise 1: Shape Hierarchy
Create an abstract Shape class and implement Circle, Rectangle, and Triangle.
Exercise 2: Employee Management
Build a complete employee management system with different employee types.
Exercise 3: Animal Kingdom
Create a multi-level inheritance hierarchy for animals.
Exercise 4: Payment System
Implement a payment processing system with multiple payment methods.
Exercise 5: Game Characters
Create a game character system using inheritance and composition.
Solutions
Try solving these exercises on your own first. Solutions will be provided in the practice section.