Lesson 3: Object-Oriented Programming Fundamentals

Understanding How Modern Software is Designed

โฑ๏ธ 90 minutes ๐Ÿ Python ๐Ÿ“š Year 11 Foundation
๐Ÿ“‹

What You'll Learn Today

Object-Oriented Programming (OOP) is the foundation of modern software development. Apps like Instagram, Spotify, and even operating systems are built using OOP principles. Today you'll understand:

  • What objects and classes are, and why they matter
  • The five core OOP concepts (encapsulation, abstraction, inheritance, polymorphism, generalization)
  • How OOP differs from procedural programming
  • UML diagrams and how to design object-oriented systems
  • Testing methodologies for ensuring code quality
  • Collaborative development practices
1

What is Object-Oriented Programming?

Imagine you're building a car racing game. You need to represent different cars, each with:

  • Properties: Color, speed, fuel level
  • Actions: Accelerate, brake, turn

Instead of writing separate code for every single car, OOP lets you create a blueprint (class) that defines what all cars have in common. Then you create individual objects (instances) from that blueprint.

๐Ÿš— Real-World Analogy

Class = Blueprint: Architectural plans for a house

Object = Instance: Actual houses built from those plans

You can build 100 houses from one blueprint, and each house is a separate object with its own unique characteristics (paint color, furniture, residents) but follows the same structure.

Key Definition

Object-Oriented Programming (OOP) is a programming paradigm that organizes code around "objects" - entities that contain both data (attributes) and behaviors (methods).

2

The Five Pillars of OOP

1. Objects and Classes

Class: A template or blueprint that defines the structure and behavior of objects

Object: A specific instance created from a class

๐Ÿ Python Example: Dog Class
PYTHON
# Define a class (blueprint)
class Dog:
    # Constructor - runs when creating a new object
    def __init__(self, name, breed, age):
        self.name = name      # Attribute
        self.breed = breed    # Attribute
        self.age = age        # Attribute

    # Methods (behaviors)
    def bark(self):
        return f"{self.name} says Woof!"

    def celebrate_birthday(self):
        self.age += 1
        return f"Happy birthday {self.name}! Now {self.age} years old."

# Create objects (instances)
dog1 = Dog("Max", "Golden Retriever", 3)
dog2 = Dog("Bella", "Poodle", 5)

# Use the objects
print(dog1.bark())                    # Max says Woof!
print(dog2.celebrate_birthday())      # Happy birthday Bella! Now 6 years old.
print(f"{dog1.name} is a {dog1.breed}")  # Max is a Golden Retriever

Explanation:

  • Dog is the class (blueprint)
  • dog1 and dog2 are objects (instances)
  • name, breed, age are attributes (data)
  • bark(), celebrate_birthday() are methods (behaviors)

2. Encapsulation (Data Hiding)

Encapsulation means bundling data and methods together, and controlling access to them. It protects data from being accidentally modified.

Think of it like: A TV remote. You don't need to know how the circuit board works inside - you just press buttons (public interface). The internal electronics are hidden (private data).

๐Ÿ”’ Python Example: Bank Account with Encapsulation
PYTHON
class BankAccount:
    def __init__(self, account_holder, initial_balance):
        self.account_holder = account_holder
        self.__balance = initial_balance  # Private attribute (double underscore)

    # Public method to deposit money
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            return f"Deposited ${amount}. New balance: ${self.__balance}"
        else:
            return "Invalid deposit amount"

    # Public method to withdraw money
    def withdraw(self, amount):
        if 0 < amount <= self.__balance:
            self.__balance -= amount
            return f"Withdrew ${amount}. Remaining balance: ${self.__balance}"
        else:
            return "Insufficient funds or invalid amount"

    # Public method to check balance
    def get_balance(self):
        return f"Current balance: ${self.__balance}"

# Create account
account = BankAccount("Ulrich", 1000)

# Use public methods
print(account.deposit(500))        # Deposited $500. New balance: $1500
print(account.withdraw(200))       # Withdrew $200. Remaining balance: $1300
print(account.get_balance())       # Current balance: $1300

# This would cause an error (trying to access private data directly):
# print(account.__balance)  # AttributeError!

Why this matters:

  • Balance can't be changed directly (protected from mistakes)
  • Must use proper methods (deposit/withdraw) which include validation
  • Internal implementation can change without breaking code that uses the class

3. Abstraction (Simplifying Complexity)

Abstraction means hiding complex implementation details and showing only essential features.

Real-world example: When you drive a car, you use the steering wheel, pedals, and gear shift. You don't need to understand how the engine combustion works or how the transmission system operates - those details are abstracted away.

๐ŸŽจ Example: Email System Abstraction
PYTHON
class EmailService:
    def send_email(self, recipient, subject, message):
        # User only calls this simple method
        self.__connect_to_server()
        self.__authenticate()
        self.__compose_email(recipient, subject, message)
        self.__transmit()
        self.__close_connection()
        return "Email sent successfully!"

    # All these complex details are hidden (abstracted)
    def __connect_to_server(self):
        # Complex networking code here
        pass

    def __authenticate(self):
        # Complex authentication logic here
        pass

    def __compose_email(self, recipient, subject, message):
        # Email formatting logic here
        pass

    def __transmit(self):
        # SMTP transmission logic here
        pass

    def __close_connection(self):
        # Cleanup code here
        pass

# User just needs one simple line!
email_service = EmailService()
email_service.send_email("friend@example.com", "Hello", "How are you?")

Benefits: Users don't need to know the complex steps - they just call one method.

4. Inheritance (Parent-Child Relationships)

Inheritance allows a class to inherit attributes and methods from another class. This promotes code reuse.

Terminology:

  • Parent Class (Superclass/Base Class): The class being inherited from
  • Child Class (Subclass/Derived Class): The class that inherits
๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง Python Example: Employee Hierarchy
PYTHON
# Parent class (base/superclass)
class Employee:
    def __init__(self, name, employee_id, salary):
        self.name = name
        self.employee_id = employee_id
        self.salary = salary

    def get_details(self):
        return f"Employee: {self.name}, ID: {self.employee_id}"

    def calculate_annual_salary(self):
        return self.salary * 12

# Child class 1 (inherits from Employee)
class Developer(Employee):
    def __init__(self, name, employee_id, salary, programming_language):
        # Call parent constructor
        super().__init__(name, employee_id, salary)
        self.programming_language = programming_language

    def write_code(self):
        return f"{self.name} is coding in {self.programming_language}"

# Child class 2 (inherits from Employee)
class Manager(Employee):
    def __init__(self, name, employee_id, salary, team_size):
        super().__init__(name, employee_id, salary)
        self.team_size = team_size

    def conduct_meeting(self):
        return f"{self.name} is leading a meeting with {self.team_size} team members"

# Create objects
dev = Developer("Alice", "E001", 5000, "Python")
mgr = Manager("Bob", "M001", 7000, 10)

# Both inherit from Employee
print(dev.get_details())                # Employee: Alice, ID: E001
print(dev.calculate_annual_salary())    # 60000
print(dev.write_code())                 # Alice is coding in Python

print(mgr.get_details())                # Employee: Bob, ID: M001
print(mgr.conduct_meeting())            # Bob is leading a meeting with 10 team members

What happened:

  • Developer and Manager inherit all attributes/methods from Employee
  • They can reuse get_details() and calculate_annual_salary()
  • Each child class also has its own unique methods

5. Polymorphism (Same Interface, Different Behavior)

Polymorphism means "many forms." The same method name can have different implementations in different classes.

Real-world analogy: A "speak" command means different things for different animals - dogs bark, cats meow, birds chirp.

๐Ÿพ Python Example: Animal Sounds
PYTHON
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass  # To be overridden by child classes

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

class Bird(Animal):
    def speak(self):
        return f"{self.name} says Chirp!"

# Create different animals
animals = [
    Dog("Max"),
    Cat("Whiskers"),
    Bird("Tweety"),
    Dog("Buddy")
]

# Polymorphism in action - same method, different behaviors
for animal in animals:
    print(animal.speak())

# Output:
# Max says Woof!
# Whiskers says Meow!
# Tweety says Chirp!
# Buddy says Woof!

The power: We can call speak() on any animal without knowing its specific type!

Bonus: Generalization

Generalization is the process of extracting common characteristics from specific classes to create a more general parent class.

It's the opposite of specialization - you identify what's common among multiple classes and move it to a parent class.

๐Ÿ“ Example: Shape Generalization

If you have Circle, Rectangle, and Triangle classes, you notice they all share:

  • Color
  • Position (x, y coordinates)
  • Methods: draw(), move()

You can generalize by creating a Shape parent class containing these common features!

The Five OOP Pillars
  • Classes & Objects: Blueprints and instances
  • Encapsulation: Data hiding and protection
  • Abstraction: Hiding complexity
  • Inheritance: Code reuse through parent-child relationships
  • Polymorphism: Same method, different implementations
3

Procedural Programming vs Object-Oriented Programming

Two Different Approaches to Programming

๐Ÿ“ Procedural Programming

Focus: Functions and procedures

Characteristics:

  • Top-down approach
  • Data and functions are separate
  • Functions operate on data
  • Global data can be accessed by any function
  • Examples: C, Pascal, early BASIC

Advantages:

  • โœ“ Simple and straightforward
  • โœ“ Easy to learn for beginners
  • โœ“ Good for small programs
  • โœ“ Efficient execution

Disadvantages:

  • โœ— Hard to manage large programs
  • โœ— Difficult to reuse code
  • โœ— Global data can cause bugs
  • โœ— Hard to model real-world entities

๐ŸŽฏ Object-Oriented Programming

Focus: Objects containing data and methods

Characteristics:

  • Bottom-up approach
  • Data and functions bundled together
  • Objects interact through messages
  • Data encapsulated in objects
  • Examples: Python, Java, C++, JavaScript

Advantages:

  • โœ“ Models real-world entities naturally
  • โœ“ Code reuse through inheritance
  • โœ“ Easier to maintain and modify
  • โœ“ Data security through encapsulation

Disadvantages:

  • โœ— Steeper learning curve
  • โœ— Can be slower than procedural
  • โœ— More complex for simple tasks
  • โœ— Requires more planning
๐Ÿ”„ Same Problem, Different Approaches

Task: Calculate area of rectangles

PROCEDURAL APPROACH
# Procedural: Functions operate on data
def calculate_area(width, height):
    return width * height

def calculate_perimeter(width, height):
    return 2 * (width + height)

# Data is separate
rectangle1_width = 10
rectangle1_height = 5
rectangle2_width = 8
rectangle2_height = 4

# Call functions with data
area1 = calculate_area(rectangle1_width, rectangle1_height)
perimeter1 = calculate_perimeter(rectangle1_width, rectangle1_height)

print(f"Area: {area1}, Perimeter: {perimeter1}")
OBJECT-ORIENTED APPROACH
# OOP: Data and methods bundled together
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def calculate_area(self):
        return self.width * self.height

    def calculate_perimeter(self):
        return 2 * (self.width + self.height)

# Create objects (data + methods together)
rectangle1 = Rectangle(10, 5)
rectangle2 = Rectangle(8, 4)

# Objects know how to calculate their own properties
print(f"Area: {rectangle1.calculate_area()}, Perimeter: {rectangle1.calculate_perimeter()}")

Key difference: In OOP, the rectangle "knows" how to calculate its own area. In procedural, you need external functions.

4

UML Diagrams - Visualizing OOP Design

UML (Unified Modeling Language) is a standard way to visualize software design. It helps you plan before coding!

Class Diagrams

A class diagram shows the structure of classes, their attributes, methods, and relationships.

Components:

  • Class Name: Top section
  • Attributes: Middle section (data/properties)
  • Methods: Bottom section (behaviors/functions)
UML Class Diagram showing Student class with three sections: class name 'Student', attributes (- name: String, - studentID: int, - gpa: float), and methods (+ enroll(course): void, + calculateGPA(): float, + getDetails(): String). Legend shows - = private, + = public
๐Ÿ“Š Reading a Class Diagram

Visibility Symbols:

  • + Public: Accessible from anywhere
  • - Private: Accessible only within the class
  • # Protected: Accessible within class and subclasses

Example:

  • - name: String means name is a private string attribute
  • + enroll(course): void means enroll is a public method that takes a course parameter and returns nothing

Relationships in Class Diagrams

Relationship Symbol Meaning Example
Inheritance Arrow with hollow triangle "Is-a" relationship Dog IS-A Animal
Association Simple line General relationship Student enrolled in Course
Aggregation Line with hollow diamond "Has-a" (weak ownership) Department HAS Students
Composition Line with filled diamond "Owns" (strong ownership) House OWNS Rooms
UML Class Diagram showing inheritance hierarchy with Vehicle parent class (attributes: make, model; methods: start(), stop()) at top, and two child classes Car (attribute: numDoors; method: openTrunk()) and Motorcycle (attribute: hasWindshield; method: popWheelie()) inheriting from Vehicle via hollow triangle arrows

Data Flow Diagrams (DFD)

A Data Flow Diagram shows how data moves through a system.

Symbols:

  • Circle/Process: An action or transformation
  • Arrow: Data flow direction
  • Rectangle: External entity (user, system)
  • Parallel lines: Data store (database, file)
Data Flow Diagram for online shopping system showing Customer (external entity) sending Order Details to Process Order (process), which saves to Database (data store), sends Order Confirmation back to Customer, and flows to Generate Invoice (process) which sends Invoice to Customer. Includes legend for symbols.

Structure Charts

Structure charts show the hierarchical organization of modules in a program.

They're useful for breaking down complex systems into manageable parts (following top-down design).

Example: A student management system might have:

  • Main Module โ†’ Student Management
  • โ†’ Add Student
  • โ†’ View Students
  • โ†’ Update Student
  • โ†’ Delete Student
  • โ†’ Calculate GPA (which further breaks into Get Grades โ†’ Compute Average)
5

OOP Design Processes

Task Definition

Before designing classes, you must clearly define what the system needs to do:

  1. Identify requirements: What problem are we solving?
  2. Define scope: What's included and excluded?
  3. List features: What must the system do?
  4. Identify users: Who will use this?
๐Ÿ“‹ Example: Library Management System

Requirements:

  • Track books (title, author, ISBN, availability)
  • Manage members (name, ID, borrowed books)
  • Handle borrowing and returning books
  • Calculate late fees
  • Search for books by title or author

Potential Classes: Book, Member, Loan, Library

Top-Down Design in OOP

Top-down design starts with the big picture and breaks it into smaller pieces:

  1. Identify the main system
  2. Break it into major subsystems (classes)
  3. Define each class's responsibilities
  4. Identify relationships between classes
  5. Refine each class's attributes and methods

Bottom-Up Design in OOP

Bottom-up design starts with individual components and builds up:

  1. Identify basic entities (simple classes)
  2. Implement and test each class independently
  3. Combine classes into larger subsystems
  4. Build the complete system from tested components

Facade Pattern

The facade pattern provides a simplified interface to a complex system of classes.

Think of it like: A restaurant menu is a facade. You order "Burger Combo" without knowing all the complex kitchen processes behind it.

๐Ÿข Facade Pattern Example
PYTHON
# Complex subsystems
class PaymentProcessor:
    def process_payment(self, amount):
        return f"Processing ${amount} payment"

class InventorySystem:
    def check_stock(self, item):
        return f"Checking stock for {item}"

    def update_stock(self, item):
        return f"Updating stock for {item}"

class ShippingService:
    def arrange_delivery(self, address):
        return f"Arranging delivery to {address}"

# Facade - provides simple interface
class OrderFacade:
    def __init__(self):
        self.payment = PaymentProcessor()
        self.inventory = InventorySystem()
        self.shipping = ShippingService()

    def place_order(self, item, amount, address):
        # User just calls one method, but complex operations happen behind the scenes
        print(self.inventory.check_stock(item))
        print(self.payment.process_payment(amount))
        print(self.inventory.update_stock(item))
        print(self.shipping.arrange_delivery(address))
        return "Order placed successfully!"

# Simple usage
order_system = OrderFacade()
order_system.place_order("Laptop", 1200, "123 Main St")

Agile Development in OOP

OOP works perfectly with Agile methodology:

  • Iterative development: Build one class at a time, test, refine
  • Flexibility: Easy to add new classes or modify existing ones
  • Encapsulation supports change: Internal changes don't break other code
  • Inheritance allows extension: Add new features by creating subclasses
6

Message Passing & Code Optimization

Message Passing Between Objects

In OOP, objects communicate by sending messages (calling methods on other objects).

Instead of accessing data directly, objects ask other objects to perform actions.

๐Ÿ“ฌ Message Passing Example
PYTHON
class Customer:
    def __init__(self, name, account):
        self.name = name
        self.account = account  # Customer HAS-A BankAccount

    def make_purchase(self, amount):
        # Customer sends a message to BankAccount object
        result = self.account.withdraw(amount)
        return f"{self.name}: {result}"

class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
            return f"Purchase successful. Remaining balance: ${self.balance}"
        else:
            return "Insufficient funds"

# Objects communicating
account = BankAccount(1000)
customer = Customer("Ulrich", account)

# Customer object sends message to BankAccount object
print(customer.make_purchase(200))  # Ulrich: Purchase successful. Remaining balance: $800

Notice: Customer doesn't directly access account.balance - it sends a message via withdraw()

Code Optimization in OOP

Optimization strategies:

  • DRY Principle (Don't Repeat Yourself): Use inheritance to avoid duplicate code
  • Lazy Loading: Only create objects when needed
  • Object Pooling: Reuse objects instead of creating new ones repeatedly
  • Efficient Data Structures: Choose appropriate data structures for your use case
โšก Optimization Example

Before (Inefficient - Repeated Code):

BAD PRACTICE
class Dog:
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(f"{self.name} is eating")

    def sleep(self):
        print(f"{self.name} is sleeping")

class Cat:
    def __init__(self, name):
        self.name = name

    def eat(self):  # Duplicate code!
        print(f"{self.name} is eating")

    def sleep(self):  # Duplicate code!
        print(f"{self.name} is sleeping")

After (Optimized - Using Inheritance):

GOOD PRACTICE
class Animal:
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(f"{self.name} is eating")

    def sleep(self):
        print(f"{self.name} is sleeping")

class Dog(Animal):
    def bark(self):
        print(f"{self.name} barks")

class Cat(Animal):
    def meow(self):
        print(f"{self.name} meows")

# Code reused, not duplicated!
7

Testing Methodologies

Testing ensures your code works correctly. In OOP, there are different levels and types of testing.

Testing Levels

Test Level What It Tests Example When to Use
Unit Testing Individual methods/classes Test if calculate_area() method returns correct value While developing each class
Subsystem Testing Groups of related classes Test if payment subsystem (PaymentProcessor + Invoice) works together After integrating related classes
System Testing Entire application Test complete e-commerce site from browsing to checkout Before deployment

Testing Techniques

Black Box vs White Box vs Grey Box Testing

Black Box Testing

Tester doesn't know internal code

  • Focus on inputs and outputs
  • Test from user perspective
  • Don't need programming knowledge
  • Example: Enter username/password, check if login succeeds

Analogy: Testing a car by driving it without opening the hood

White Box Testing

Tester knows internal code structure

  • Test internal logic and paths
  • Ensure all code branches execute
  • Requires programming knowledge
  • Example: Check if all IF/ELSE branches in login method are tested

Analogy: Testing a car by inspecting engine, wiring, etc.

Grey Box Testing

Combination of both approaches

  • Partial knowledge of internal structure
  • Design tests based on architecture
  • More comprehensive than black box alone
  • Example: Know that login checks database, test with valid/invalid credentials and database errors

Best for: Integration testing between modules

๐Ÿงช Unit Testing Example
PYTHON (using unittest module)
import unittest

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def calculate_area(self):
        return self.width * self.height

# Unit tests for Rectangle class
class TestRectangle(unittest.TestCase):

    def test_area_positive_numbers(self):
        rect = Rectangle(5, 10)
        self.assertEqual(rect.calculate_area(), 50)

    def test_area_with_zero(self):
        rect = Rectangle(0, 10)
        self.assertEqual(rect.calculate_area(), 0)

    def test_area_decimals(self):
        rect = Rectangle(5.5, 2.0)
        self.assertEqual(rect.calculate_area(), 11.0)

# Run tests
if __name__ == '__main__':
    unittest.main()

Benefits: Automated tests catch bugs early and ensure changes don't break existing functionality.

8

Collaborative OOP Development

Version Control

Version control systems (like Git) are essential for team collaboration:

  • Track changes: See who changed what and when
  • Branching: Work on features independently
  • Merging: Combine code from different developers
  • Rollback: Undo mistakes by reverting to previous versions
๐ŸŒฟ Git Workflow for OOP Projects
  1. Developer A creates a branch to implement User class
  2. Developer B creates a branch to implement Product class
  3. Both work independently without conflicts
  4. When done, both merge their branches into main branch
  5. If there are conflicts (e.g., both modified same file), Git helps resolve them

Code Commenting Best Practices

Good comments make collaboration easier:

  • Class-level comments: Explain what the class represents
  • Method docstrings: Describe parameters, return values, purpose
  • Inline comments: Explain complex logic (use sparingly)
๐Ÿ“ Well-Commented Code Example
PYTHON
class ShoppingCart:
    """
    Represents a shopping cart in an e-commerce system.

    Handles adding/removing items and calculating totals.
    """

    def __init__(self):
        """Initialize empty shopping cart."""
        self.items = []

    def add_item(self, product, quantity):
        """
        Add a product to the cart.

        Args:
            product (Product): The product object to add
            quantity (int): Number of units to add

        Returns:
            bool: True if successful, False if invalid quantity
        """
        if quantity <= 0:
            return False

        self.items.append({'product': product, 'quantity': quantity})
        return True

    def calculate_total(self):
        """
        Calculate total cost of all items in cart.

        Returns:
            float: Total price including all items
        """
        total = 0
        for item in self.items:
            total += item['product'].price * item['quantity']
        return total

Code Consistency

Teams establish coding standards to maintain consistency:

  • Naming conventions: Classes use PascalCase, methods use snake_case
  • File organization: One class per file, related classes in same directory
  • Style guides: Python follows PEP 8 standards
  • Code reviews: Team members review each other's code before merging
Collaboration Success Factors
  • Use version control (Git) religiously
  • Write clear, descriptive comments and docstrings
  • Follow team coding standards consistently
  • Conduct regular code reviews
  • Communicate design decisions with team
๐ŸŽ“

Knowledge Check

Test Your Understanding
Question 1: What are the five core OOP concepts?
Answer:
  1. Classes & Objects: Templates and instances
  2. Encapsulation: Data hiding and bundling
  3. Abstraction: Hiding complexity
  4. Inheritance: Parent-child relationships for code reuse
  5. Polymorphism: Same method name, different implementations
Question 2: Explain the difference between a class and an object.
Answer:

Class: A blueprint or template that defines structure and behavior (like architectural plans for a house)

Object: A specific instance created from a class (like an actual house built from those plans)

Example: Dog is a class; my_dog = Dog("Max") creates an object.

Question 3: What is encapsulation and why is it important?
Answer:

Encapsulation is bundling data and methods together and restricting direct access to internal data.

Importance:

  • Protects data from accidental modification
  • Enforces controlled access through methods
  • Allows internal changes without breaking external code
  • Improves security and maintainability
Question 4: How does OOP differ from procedural programming?
Answer:

Procedural:

  • Focuses on functions/procedures
  • Data and functions are separate
  • Top-down approach

OOP:

  • Focuses on objects (data + methods bundled)
  • Bottom-up approach
  • Better for modeling real-world entities
  • Easier code reuse through inheritance
Question 5: What's the difference between white box and black box testing?
Answer:

Black Box Testing:

  • Tester doesn't know internal code structure
  • Tests based on inputs/outputs only
  • User perspective testing

White Box Testing:

  • Tester knows and examines internal code
  • Tests code paths, branches, logic
  • Developer perspective testing
๐Ÿ’ช

Practice Exercises

๐ŸŽฏ Exercise 1: Design a Class Hierarchy

Scenario: Design a class hierarchy for a university system.

  1. Create a Person base class with name and age
  2. Create Student class (inherits from Person) with student_id and gpa
  3. Create Professor class (inherits from Person) with employee_id and department
  4. Add appropriate methods to each class

Challenge: Draw a UML class diagram for your design before coding!

๐ŸŽฏ Exercise 2: Implement Encapsulation

Create a Temperature class that:

  • Stores temperature in Celsius (private attribute)
  • Has methods to get/set temperature in Celsius
  • Has methods to get/set temperature in Fahrenheit
  • Validates that temperature is not below absolute zero (-273.15ยฐC)

Hint: Formula: F = C ร— 9/5 + 32

๐ŸŽฏ Exercise 3: Demonstrate Polymorphism

Create a Shape parent class with:

  • Child classes: Circle, Rectangle, Triangle
  • Each class should have a calculate_area() method
  • Create a list of different shapes and calculate total area

Formulas:

  • Circle: ฯ€ ร— rยฒ
  • Rectangle: width ร— height
  • Triangle: (base ร— height) / 2
๐ŸŽฏ Exercise 4: Write Unit Tests

For the BankAccount class example from this lesson, write unit tests to check:

  1. Deposit with positive amount works correctly
  2. Deposit with negative amount is rejected
  3. Withdraw with sufficient funds works
  4. Withdraw with insufficient funds is rejected
  5. Balance is correctly updated after transactions
๐Ÿ“‹

Lesson Summary

What We Covered Today

1. Core OOP Concepts:

  • Classes (blueprints) and Objects (instances)
  • Encapsulation (data hiding and bundling)
  • Abstraction (hiding complexity)
  • Inheritance (code reuse through parent-child relationships)
  • Polymorphism (same interface, different implementations)

2. Procedural vs OOP:

  • Procedural focuses on functions; OOP focuses on objects
  • OOP better for complex systems and real-world modeling
  • OOP provides better code reuse and maintainability

3. UML Diagrams:

  • Class diagrams show structure and relationships
  • Data flow diagrams show how data moves through systems
  • Structure charts show hierarchical module organization

4. Design Processes:

  • Top-down: Start with big picture, break down
  • Bottom-up: Build components, combine upward
  • Facade pattern simplifies complex subsystems
  • Agile methodology works well with OOP

5. Testing & Collaboration:

  • Unit, subsystem, and system testing levels
  • Black box (external), white box (internal), grey box (hybrid) approaches
  • Version control, code comments, and consistency standards
๐Ÿ“š

Homework & Next Steps

Before Next Lesson
  1. Complete all practice exercises from this lesson
  2. Design a small OOP project: Choose a real-world system (library, restaurant, school) and:
    • Identify at least 3 classes
    • Draw a UML class diagram
    • List attributes and methods for each class
    • Show relationships (inheritance, association)
  3. Research: Find an open-source Python project on GitHub and identify:
    • How they use inheritance
    • Examples of encapsulation
    • Any design patterns they use
  4. Review: Go back to Lesson 2 and think about how data structures (arrays, records, trees) relate to OOP classes

Next Lesson Preview: We'll dive into Year 12 content - Secure Software Architecture! You'll learn about the CIA Triad, common vulnerabilities, defensive programming, and how to build secure applications from the ground up.

โ† Back to Ulrich's Home