Day 14: OOP — Inheritance, Encapsulation & Polymorphism
🐍 Day 14: OOP — Inheritance, Encapsulation & Polymorphism
1. Learning Objectives
By the end of Day 14, you will be able to:
- Use inheritance to create child classes that reuse and extend parent functionality
- Override methods and use
super()to call parent methods - Apply encapsulation to control access to attributes (private/protected conventions)
- Write properties with the
@propertydecorator for controlled access - Understand polymorphism — different classes sharing a common interface
- Design object‑oriented systems that are flexible, maintainable, and safe
2. Concept Explanation
2.1 Inheritance — Building on What Already Exists
In real life, a sedan is a type of car, which is a type of vehicle. Inheritance models these "is‑a" relationships. A child class inherits all attributes and methods from its parent, then adds or modifies as needed.
class Vehicle: # Parent (base) class
def __init__(self, brand):
self.brand = brand
def start(self):
print(f"{self.brand} vehicle started.")
class Car(Vehicle): # Child class inherits from Vehicle
def __init__(self, brand, model):
super().__init__(brand) # Call parent's __init__
self.model = model
def honk(self):
print("Beep beep!")
Carinheritsstart()fromVehicle.super().__init__(brand)runs the parent’s constructor, soself.brandis set correctly.
2.2 Method Overriding & super()
A child class can override (replace) a parent method:
class ElectricCar(Car):
def start(self):
print(f"{self.brand} {self.model} starts silently.")
my_tesla = ElectricCar("Tesla", "Model 3")
my_tesla.start() # "Tesla Model 3 starts silently."
If you still need the parent’s version, call super():
class HybridCar(Car):
def start(self):
super().start() # Runs Car's start (which runs Vehicle's)
print("Electric motor engaged.")
2.3 Encapsulation — Protecting Your Data
Encapsulation hides internal details and prevents accidental changes. Python uses naming conventions, not strict enforcement, to signal privacy:
| Convention | Meaning |
|---|---|
self._value | Protected — "please don't touch, but you can if you know what you're doing" |
self.__value | Private — name mangling makes it harder to access from outside (but still possible) |
class BankAccount:
def __init__(self, owner):
self.owner = owner
self._balance = 0 # protected
self.__pin = "1234" # private (name mangled to _BankAccount__pin)
def deposit(self, amount):
if amount > 0:
self._balance += amount
def get_balance(self):
return self._balance
Accessing self.__pin from outside raises AttributeError (unless you use the mangled name). This discourages direct access.
2.4 Properties: Controlled Attribute Access
Instead of writing get_balance() and set_balance() methods manually, use properties with the @property decorator:
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
"""Getter — read the temperature"""
return self._celsius
@celsius.setter
def celsius(self, value):
"""Setter — validate before changing"""
if value < -273.15:
raise ValueError("Below absolute zero!")
self._celsius = value
@property
def fahrenheit(self):
"""Computed property — no setter needed"""
return self._celsius * 9/5 + 32
Usage:
t = Temperature(25)
print(t.celsius) # 25 (calls getter)
t.celsius = 30 # (calls setter, validation)
print(t.fahrenheit) # 86.0 (computed)
# t.fahrenheit = 100 # AttributeError: no setter defined
Properties make your code Pythonic and safe.
2.5 Polymorphism — One Interface, Many Forms
Polymorphism means different classes can be used interchangeably if they share the same method names. Python relies on duck typing: "If it walks like a duck and quacks like a duck, it's a duck."
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
def animal_sound(animal):
print(animal.speak())
animal_sound(Dog()) # Woof!
animal_sound(Cat()) # Meow!
The animal_sound() function doesn't care what type animal is — only that it has a speak() method. This is powerful for writing flexible code.
2.6 When to Use What
| Feature | Use Case |
|---|---|
| Inheritance | When a class is a specialised version of another (e.g., Student → GraduateStudent) |
| Composition | "Has‑a" relationship — often better than deep inheritance chains (e.g., Library has Books, not Library is a Book) |
| Encapsulation | Protect internal state, expose only safe interfaces |
| Properties | Add validation or computed values without breaking existing code |
| Polymorphism | When you want to process objects generically based on their behaviour |
3. Code Examples
Example 1: Inheritance Hierarchy
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def work(self):
return f"{self.name} is working."
class Manager(Employee):
def __init__(self, name, salary, department):
super().__init__(name, salary)
self.department = department
def work(self):
return f"{self.name} is managing the {self.department} department."
class Developer(Employee):
def __init__(self, name, salary, language):
super().__init__(name, salary)
self.language = language
def work(self):
return f"{self.name} is coding in {self.language}."
# Polymorphism in action
team = [Manager("Alice", 90000, "Sales"),
Developer("Bob", 80000, "Python"),
Developer("Charlie", 85000, "JavaScript")]
for member in team:
print(member.work())
Example 2: Encapsulation with Properties
class Person:
def __init__(self, name, age):
self.name = name
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not (0 <= value <= 150):
raise ValueError("Invalid age")
self._age = value
p = Person("Alice", 28)
print(p.age) # 28
p.age = 30 # valid
# p.age = -5 # raises ValueError
Example 3: Composition over Inheritance
class Battery:
def __init__(self, capacity):
self.capacity = capacity
def info(self):
return f"{self.capacity} kWh battery"
class ElectricCar:
def __init__(self, brand, battery):
self.brand = brand
self.battery = battery # composition: has a battery
b = Battery(75)
car = ElectricCar("Tesla", b)
print(car.battery.info()) # 75 kWh battery
4. Hands-On Exercises
Exercise 1: Animal Inheritance
Create a base class Animal with a make_sound() method (just pass). Then create Dog and Cat subclasses that override make_sound() to return "Woof" and "Meow". Demonstrate polymorphism by iterating over a list of animals.
Exercise 2: Shape Hierarchy
Define a Shape class with an area() method that returns 0. Create Rectangle(width, height) and Circle(radius) subclasses that override area(). Use math.pi for the circle. Create a list of shapes and print their areas.
Exercise 3: Encapsulation with Account
Build a BankAccount class with a private __balance attribute (using name mangling). Provide deposit(amount) and withdraw(amount) methods. Add a property balance that only provides a getter (no setter). Test that direct assignment to balance raises an error.
Exercise 4: Property Validation
Create a Product class with name and _price. Use a property for price that validates: price must be >= 0. Raise ValueError if negative.
Exercise 5: Polymorphic Function
Write a function describe(vehicle) that prints the result of a description() method. Create Car and Bicycle classes both with description(). Show that the function works with both.
5. Applied Challenge Task 🏗️
Employee Management System
Design an OOP‑based employee management system that demonstrates inheritance, encapsulation, and polymorphism.
Requirements:
Base class
Employee:- Attributes:
name(public),_salary(protected) - Property
salarywith getter and a setter that ensures salary is never negative. - Method
work()that returns a generic string like"Employee is working."
- Attributes:
Subclass
Manager(Employee):- Additional attribute
department(public) - Override
work()to return"{name} is managing the {department} department." - Add a method
hold_meeting()that prints"{name} is holding a meeting."
- Additional attribute
Subclass
Developer(Employee):- Additional attribute
programming_language(public) - Override
work()to return"{name} is writing code in {programming_language}."
- Additional attribute
Class
Company:- Holds a list of employees (composition).
- Methods:
add_employee(employee),list_all_employees(),total_salary_expense()(sum of all salaries).
Menu‑driven interface:
- Add Manager / Add Developer
- List all employees (showing their
work()result) - Show total salary expense
- Quit
Stretch goals:
- Add CSV or JSON file persistence (load/store company data)
- Add an
Intern(Employee)subclass with a fixed salary (no setter) - Use
@classmethodto create an alternative constructor (e.g.,Developer.from_string("Bob,85000,Python"))
6. Brief Review Summary
| Concept | Key Points |
|---|---|
| Inheritance | Child class inherits from parent; "is‑a" relationship |
super() | Calls parent methods, especially in __init__ |
| Method overriding | Child provides its own version of a parent method |
| Encapsulation | Use _ (protected) and __ (private) conventions; prefer properties |
@property | Turns a method into an attribute with getter/setter/deleter |
| Polymorphism | Different objects respond to the same method call; duck typing |
| Composition | "Has‑a" relationship — often more flexible than inheritance |
7. Preview of Next Topic — Day 15
Tomorrow we wrap up Phase 2 with a Weekly Mini‑Project:
- An OOP‑based system (e.g., inventory, banking, task manager) that combines all Phase 2 concepts
- Apply classes, inheritance, encapsulation, polymorphism, file handling, exception handling
- Emphasis on clean architecture and reusability
🎯 Your Action Items for Day 14:
- ✅ Complete all 5 exercises
- ✅ Build the Employee Management System challenge
- ✅ Experiment with
super()– call a parent method before/after your custom code - ✅ Try creating a private attribute
__secretand access it from outside using the mangled name
Comments
Post a Comment
Leave us your comments here...