- Bundle data and methods that operate on that data
- Hide internal implementation details
- Expose only what's needed via public interface
- Use getters/setters or
@propertyfor controlled access
This presentation is designed for desktop or projector displays.
← Back to curriculumPython Object Oriented Programming
Course 5
← → to navigate
Where we are in the journey
Which prefix makes an attribute private in Python?
A) No prefix
B) _single underscore
C) __double underscore
D) #hash
Double underscore triggers name mangling, making the attribute harder to access from outside
Recap: Lecture 02
Accessible from anywhere. No underscores.
self.engine_status = "Off"
Accessible within class and subclasses. Convention only.
self._fuel_level = 50
Accessible within class only. Name mangling.
self.__owner = "John"
What is the main purpose of encapsulation?
A) Making code run faster
B) Hiding internal state and requiring interaction through methods
C) Allowing multiple inheritance
D) Creating abstract classes
Encapsulation bundles data with methods and restricts direct access — protecting the object's internal state from unintended changes
Recap: Lecture 02
@property for controlled accessclass BankAccount: def __init__(self, balance): self.__balance = balance @property def balance(self): return self.__balance def deposit(self, amount): if amount > 0: self.__balance += amount
What does super().__init__() do in a child class?
A) Creates a new parent object
B) Calls the parent's constructor
C) Overrides the parent method
D) Deletes the parent class
super() gives access to the parent class — calling __init__() ensures the parent's setup runs before the child adds its own
Recap: Lecture 03
super() to call parent methodsclass Animal: def __init__(self, name): self.name = name class Dog(Animal): def __init__(self, name, breed): super().__init__(name) self.breed = breed
Which type of inheritance involves a child with multiple parents?
A) Single inheritance
B) Hierarchical inheritance
C) Multiple inheritance
D) Multilevel inheritance
Multiple inheritance = one child inherits from two or more parents. Python resolves method conflicts via MRO (Method Resolution Order)
Recap: Lecture 03
One parent → one child
class Dog(Animal): pass
Multiple parents → one child
class C(A, B): pass
Grandparent → parent → child
class Puppy(Dog): pass
What happens when a child class defines a method with the same name as the parent?
A) Both methods run
B) The child's method overrides the parent's
C) A syntax error occurs
D) The parent's method always wins
This is method overriding — the child provides its own implementation, which takes precedence when called on the child instance
Recap: Lecture 04
for shape in [Circle(5), Square(4)]: print(shape.area()) # Each shape calculates its own area
What is the purpose of an abstract class?
A) To create objects directly
B) To store data in a database
C) To define a contract that subclasses must implement
D) To prevent inheritance
Abstract classes define methods that subclasses must implement — you cannot instantiate an abstract class directly
Recap: Lecture 04
ABC and @abstractmethodfrom abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass class Circle(Shape): def area(self): return 3.14 * self.r ** 2
Four pillars we've covered
Bundling data + methods, hiding internal state
Child classes reuse and extend parent behavior
Same interface, different implementations
Exposing only essential features, hiding complexity
Two fundamental types
A subclass is a type of its parent
An object has a reference to another object
Part 1
Strong "has-a" — parts cannot exist without the whole
A heart cannot exist outside the body. The body owns the heart.
Engine is built for this car. Car is destroyed → engine goes with it.
Rooms don't exist without the hotel. Exclusive ownership.
class Engine: def __init__(self, horsepower): self.horsepower = horsepower def start(self): return "Engine running" class Car: def __init__(self, model): self.model = model self.engine = Engine(200) def drive(self): return self.engine.start()
Car creates the Engine inside __init__Car object is deleted, the Engine goes with it# Usage my_car = Car("Toyota") print(my_car.drive()) # Engine running
class Wheel: def __init__(self, position): self.position = position class Transmission: def __init__(self, type_): self.type = type_ class Car: def __init__(self, model): self.model = model self.engine = Engine(200) self.transmission = Transmission("automatic") self.wheels = [Wheel(pos) for pos in ["FL", "FR", "RL", "RR"]]
Part 2
Weak "has-a" — parts can exist independently
Airline goes bankrupt → planes are sold to another airline. Planes survive.
Close the library → books are donated. Books exist independently.
Students can belong to multiple organizations. Not exclusive.
class Book: def __init__(self, title): self.title = title class Library: def __init__(self, name): self.name = name self.books = [] def add_book(self, book): self.books.append(book)
add_book()# Usage b1 = Book("Python 101") b2 = Book("Clean Code") lib = Library("City Library") lib.add_book(b1) lib.add_book(b2) del lib print(b1.title) # Python 101 still exists!
| Composition | Aggregation | |
|---|---|---|
| Relationship | Strong "has-a" | Weak "has-a" |
| Ownership | Exclusive | Shared |
| Lifecycle | Parts die with owner | Parts live independently |
| Creation | Created inside owner | Passed in from outside |
| Coupling | Tight | Loose |
| Example | House & Rooms | Team & Players |
class House: def __init__(self): # Created INSIDE self.rooms = [ Room("kitchen"), Room("bedroom"), Room("bathroom"), ] house = House() del house # rooms are gone too
class Library: def __init__(self): self.books = [] def add_book(self, book): # Passed in from OUTSIDE self.books.append(book) book = Book("Python 101") lib = Library() lib.add_book(book) del lib print(book.title) # still exists!
Part 3
How tightly objects depend on each other
class MySQLDatabase: def query(self, sql): return f"MySQL: {sql}" class UserService: def __init__(self): self.db = MySQLDatabase() def get_user(self, id): return self.db.query( f"SELECT * FROM users WHERE id={id}" )
UserService creates MySQLDatabase internallyclass UserService: def __init__(self, db): self.db = db def get_user(self, id): return self.db.query( f"SELECT * FROM users WHERE id={id}" ) # Easy to swap implementations svc = UserService(MySQLDatabase()) svc = UserService(PostgresDatabase()) svc = UserService(MockDatabase())
From strong ownership to temporary use
Strong ownership
Parts die with owner
Weak ownership
Parts live independently
Temporary use
No ownership at all
Part 4
Prefer composing objects over deep class hierarchies
A principle suggesting classes should achieve polymorphism through composition rather than inheritance
class Employee: def __init__(self, name): self.name = name class Manager(Employee): def generate_report(self): return "Manager report" class Technician(Employee): def repair_equipment(self): return "Equipment repaired"
Employee has a Role — roles are pluggable objects
class ManagerRole: def perform_duty(self): return "Manager report" class TechnicianRole: def perform_duty(self): return "Equipment repaired" class Employee: def __init__(self, name, role=None): self.name = name self.role = role def perform_duty(self): if self.role: return self.role.perform_duty() return "No specific duty"
# Create roles independently mgr_role = ManagerRole() tech_role = TechnicianRole() # Assign to employees alice = Employee("Alice", mgr_role) bob = Employee("Bob", tech_role) print(alice.perform_duty()) # Manager report print(bob.perform_duty()) # Equipment repaired # Change role at runtime! alice.role = tech_role print(alice.perform_duty()) # Equipment repaired
Change behavior at runtime by swapping components
New behaviors = new components, not new subclasses
Test each component independently
Components can be shared across different classes
Part 5
When one class uses methods of another temporarily
class Printer: def print_document(self, text): print(f"Printing: {text}") class Report: def __init__(self, content): self.content = content def send_to_printer(self, printer): printer.print_document(self.content)
Report doesn't own the Printer# Usage printer = Printer() report = Report("Q1 Results") report.send_to_printer(printer) # Report has no reference to printer
Part 6
Providing dependencies from outside instead of creating them inside
You order from Amazon. How does it get to you?
Order
Warehouse
Transport
You
class OrderService: def __init__(self): self.shipper = Omniva() # hardcoded! def ship_order(self, order): self.shipper.deliver(order)
...we could inject any shipping provider? The OrderService shouldn't care which carrier delivers.
class Omniva: def deliver(self, order): return f"Omniva ships {order}" class LPExpress: def deliver(self, order): return f"LP Express ships {order}" class OrderService: def __init__(self, shipper): self.shipper = shipper def ship_order(self, order): return self.shipper.deliver(order)
# Inject Omniva svc = OrderService(Omniva()) print(svc.ship_order("Laptop")) # Omniva ships Laptop # Inject LP Express instead svc = OrderService(LPExpress()) print(svc.ship_order("Laptop")) # LP Express ships Laptop # Inject a mock for testing svc = OrderService(MockShipper()) print(svc.ship_order("Laptop")) # MockShipper ships Laptop
Passed via __init__. Most common.
class App: def __init__(self, db): self.db = db
Assigned after creation via a method.
class App: def set_db(self, db): self.db = db
Passed per method call. Temporary.
class App: def query(self, db): db.execute()
Combine DI with ABC for a proper contract
from abc import ABC, abstractmethod class Shipper(ABC): @abstractmethod def deliver(self, order): pass class Omniva(Shipper): def deliver(self, order): return f"Omniva: {order}" class DHL(Shipper): def deliver(self, order): return f"DHL: {order}"
class OrderService: def __init__(self, shipper: Shipper): self.shipper = shipper def ship(self, order): return self.shipper.deliver(order)
Shipper ABC defines the contractdeliver() can be injected| Type | Strength | Lifecycle | Example |
|---|---|---|---|
| Inheritance | is-a | Bound to parent type | Dog is-a Animal |
| Composition | Strong has-a | Parts die with owner | House has Rooms |
| Aggregation | Weak has-a | Parts survive | Team has Players |
| Dependency | Uses | Temporary | Report uses Printer |
A House creates its Room objects inside __init__. When the house is deleted, the rooms are gone. What is this?
A) Composition
B) Aggregation
C) Dependency
D) Inheritance
Composition — parts are created inside the owner and share its lifecycle. Filled diamond ♦️ in UML.
What relationship does this code show?
class Team: def __init__(self, name): self.name = name self.members = [] def add_member(self, person): self.members.append(person)
A) Composition
B) Aggregation
C) Dependency
D) Inheritance
Aggregation — person is created outside and passed in. Members survive if the team is deleted.
Which snippet shows tight coupling?
A)
class App: def __init__(self): self.db = MySQL()
B)
class App: def __init__(self, db): self.db = db
A) is tight coupling — the class creates its own dependency. B) injects it from outside, making it easy to swap and test.
What relationship does this code show?
class Report: def export(self, formatter): return formatter.format(self.data)
A) Composition
B) Aggregation
C) Dependency
D) Inheritance
Dependency — formatter is passed as a method parameter, used temporarily, and not stored. The weakest form of coupling.
Is Car → Engine composition or aggregation?
Car factory builds the engine inside the car. Engine is exclusive to this car.
class Car: def __init__(self): self.engine = Engine(200)
Engine is sourced from a supplier and installed. Can be swapped out.
class Car: def __init__(self, engine): self.engine = engine
Does the owner create and destroy the part?
→ Composition — House creates Rooms
Does the part exist independently and get passed in?
→ Aggregation — Library receives Books
Is it only used temporarily in a method call?
→ Dependency — Report uses Printer
Live Coding
Composition + Aggregation side by side
Stadium creates its Pitch & Seats
Delete stadium → pitch is gone
Teams have Players passed in
Messi plays for club + country
Next up: Exception Handling & Logging