Resources
🛠️ Course Tools & Specifications 🔄 SDLC Guide 📖 Glossary
Year 12
🔒 Secure Software Architecture 🌐 Programming for the Web ⚙️ Software Automation 📋 Software Engineering Project
Year 11
📐 Programming Fundamentals 🧩 Object-Oriented Paradigm 🤖 Programming Mechatronics

🧩 The Object-Oriented Paradigm

Mastering the design and implementation of software through interacting objects, modularity, and advanced abstraction techniques.

📗 Year 11 — Preliminary 🕐 ~10 weeks Outcomes: SE-11-01, 02, 04, 06, 07, 08
Syllabus Part 1 🧩 Understanding OOP

🧩 Key Features of Object-Oriented Programming

🎯 (SE-11-01, SE-11-02, SE-11-08)

📌 Apply the key features of an object-oriented programming language to design and implement software solutions.

🏗️ Objects and Classes

Objects are individual instances of data and behaviour that exist within a running program. A class is a blueprint or template that defines the structure and behaviour of all objects created from it. Classes specify what attributes (data) and methods (behaviour) objects will have. For example, a Car class defines that all car objects will have attributes like colour and speed, and methods like accelerate() and brake().

🔐 Encapsulation

Encapsulation is the process of bundling data (attributes) and the methods that operate on that data into a single unit (the class). It involves data hiding by using access modifiers (private, protected, public) to control which parts of the code can directly access an object's internal state. This ensures that data can only be changed through validated methods (setters), which maintains data integrity and security.

🔍 Abstraction

Abstraction focuses on hiding complex implementation details and showing only the essential features of an object to the user. A programmer interacts with an object through a simplified interface without needing to understand the underlying code logic. This reduces system complexity and allows internal implementation to change without affecting code that uses the object.

🧬 Inheritance and Generalisation

Inheritance allows a new class (subclass or derived class) to adopt the attributes and methods of an existing class (superclass or base class). Generalisation is the process of identifying common traits among multiple classes and moving them into a shared parent class. This promotes massive code reuse and establishes a logical hierarchy within the software architecture.

🎭 Polymorphism

Polymorphism, meaning "many forms", allows different classes to be treated as instances of the same superclass through a uniform interface. A programmer can call the same method name on different objects, and each object will respond in its own specific way. This makes code highly flexible and easier to extend with new object types without modifying existing code.

Python — Applying OOP Key Features
class Account: # Class definition (blueprint)
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance # Encapsulation (private)

    def get_balance(self): # Abstraction - interface
        return self.__balance

class SavingsAccount(Account): # Inheritance
    def apply_interest(self):
        # Specialised behaviour
        pass

acc1 = Account("Alice", 1000) # Object instance
acc2 = SavingsAccount("Bob", 2000)

def display_account(acc): # Polymorphism
    print(f"Owner: {acc.owner}")

⚖️ Comparing Procedural and Object-Oriented Programming

🎯 (SE-11-02)

📌 Compare procedural programming with the Object-Oriented Paradigm (OOP). While procedural programming focuses on a top-down sequence of actions (functions) acting upon data, OOP organises code into objects that combine data (attributes) and behaviour (methods). Understanding these differences helps engineers choose the right paradigm for each project.

Aspect Procedural Programming Object-Oriented Programming
Core Focus Step-by-step logic and function calls. Interaction between modular objects.
Data Handling Data is separate and often global. Data and methods are encapsulated.
Reusability Limited; relies on copying functions. High; via inheritance and classes.
Maintenance Challenging; one change ripples through. Easier; changes are isolated in classes.

⚖️ Diagram - Programming Paradigm Comparison

Contrasting procedural and object-oriented approaches

This diagram compares two major programming paradigms. Procedural programming focuses on "how" — breaking problems into functions and procedures with separate data and logic. Object-Oriented programming focuses on "what" — modeling problems as objects that encapsulate both data and behavior. Both approaches share benefits like modularity, reusability, and maintainability.

flowchart LR subgraph PROC["🔧 Procedural"] direction TB P1["Functions operate on
separate global data"] P2["Top-down design
focus: how"] P3["Examples
C · Pascal · procedural Python"] P1 --- P2 --- P3 end subgraph SHARED["🎯 Shared Goals"] direction TB S1["Modularity & reuse"] S2["Maintainability"] S3["Testability & debugging"] S1 --- S2 --- S3 end subgraph OOP["🧩 Object-Oriented"] direction TB O1["Classes bundle
data + methods"] O2["Four pillars
encapsulation · inheritance
polymorphism · abstraction"] O3["Examples
Java · Python · C++ · C#"] O1 --- O2 --- O3 end PROC --> SHARED --> OOP classDef proc fill:#e3f2fd,stroke:#1976D2,stroke-width:2px classDef shared fill:#f3e5f5,stroke:#6A1B9A,stroke-width:2px classDef oop fill:#e8f5e9,stroke:#2E7D32,stroke-width:2px class P1,P2,P3 proc class S1,S2,S3 shared class O1,O2,O3 oop

Purpose: Compare the fundamental differences between procedural and OOP paradigms
Syllabus Link: SE-11-02
Try This: Think of a project you know. Would procedural or OOP be better? Why? List the advantages that matter most for your use case.


🧱 Apply: The Four Pillars of OOP

🎯 (SE-11-01, SE-11-02)

📌 Apply the key features of an OOP language to engineer modular, secure, and maintainable software solutions.

🔐 Encapsulation

Encapsulation is the process of bundling data and the methods that operate on that data into a single unit, known as a class. It involves "data hiding" by using access modifiers (like private attributes in Python) to prevent external code from directly modifying an object's internal state. This ensures that data can only be changed through validated methods (setters), which maintains the integrity and security of the system (Outcome SE-11-04).

🔍 Abstraction

Abstraction focuses on hiding complex implementation details and showing only the essential features of an object to the user. In software engineering, this allows developers to interact with a class through a simplified interface without needing to understand the underlying code logic. It reduces system complexity and allows the internal implementation to change without affecting the parts of the program that use it (Outcome SE-11-01).

🧬 Inheritance & Generalisation

Inheritance allows a new class (subclass) to adopt the attributes and methods of an existing class (superclass). Generalisation is the engineering process of identifying common traits among multiple classes and moving them into a shared parent class. This promotes massive code reuse and establishes a logical hierarchy within the software architecture (Outcome SE-11-02).

🎭 Polymorphism

Polymorphism, meaning "many forms", allows different classes to be treated as instances of the same superclass through a uniform interface. A programmer can call the same method name on different objects, and each object will respond in its own specific way. This makes the code highly flexible and easier to extend with new object types (Outcome SE-11-08).

Python — Applying OOP Pillars
class Account: # Generalisation / Abstraction
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance # Encapsulation (Private)

    def get_balance(self): return self.__balance

class Savings(Account): # Inheritance
    def apply_interest(self):
        # Logic for interest...
        pass

def show_info(acc): # Polymorphism
    print(f"Owner: {acc.owner}")

🔐 Diagram - Encapsulation

Public, protected, and private access control

This diagram shows how encapsulation uses access modifiers (public, protected, private) to control data visibility and prevent unauthorized modification. External code can access public interfaces but is protected from private implementation details. This separation allows internal implementation changes without breaking external code.

graph TB subgraph BankAccount["BankAccount Class
━━━━━━━━━━━"] direction LR PUB["🟢 PUBLIC
• owner: String
• get_balance()"] PROT["🟡 PROTECTED
• _account_type"] PRIV["🔴 PRIVATE
• __balance
• __pin"] METHODS["📋 Setter Methods
• __validate()
• __update_log()"] end EXTERNAL["External Code
user_account"] EXTERNAL -->|Can access| PUB EXTERNAL -->|Cannot access| PROT EXTERNAL -->|Cannot access| PRIV EXTERNAL -->|Calls| METHODS METHODS -->|Validates <
Updates| PRIV style BankAccount fill:#FFF9C4,stroke:#F57F17,stroke-width:2px style PUB fill:#C8E6C9,stroke:#2E7D32,stroke-width:2px style PROT fill:#FFE082,stroke:#F57F17,stroke-width:2px style PRIV fill:#FFCCBC,stroke:#E64A19,stroke-width:2px style METHODS fill:#BBDEFB,stroke:#1976D2,stroke-width:2px style EXTERNAL fill:#F3E5F5,stroke:#6A1B9A,stroke-width:2px

Purpose: Demonstrate access modifiers (public, protected, private) and how encapsulation protects internal state by forcing external code to use validated setter methods
Syllabus Link: SE-11-02, SE-11-04
Try This: Design a Student class with private GPA, and implement a public method to safely update it (only if marks are within 0-100 range)

🧱 Class Diagram - Vehicle System Hierarchy

Inheritance and polymorphism in action

This class diagram shows a vehicle inheritance hierarchy where Vehicle is the parent class defining common properties (manufacturer, speed) and methods. Car and Truck inherit from Vehicle and add specialized attributes (numDoors, cargoCapacity). Both override the start() method differently, demonstrating polymorphism — same method name, different behavior depending on the subclass.

classDiagram direction TB class Vehicle { <> -String manufacturer -float speed -float fuelLevel +start() void +stop() void +accelerate(double delta) void } class Car { -int numDoors -String trunkSize +openTrunk() void +closeTrunk() void } class Motorcycle { -boolean hasSidecar +wheelie() void } class Truck { -float cargoCapacity -float currentLoad +loadCargo(float amount) void +unloadCargo(float amount) void } class Engine { -String engineType -float displacement +calculatePower() float } Vehicle <|-- Car : inherits Vehicle <|-- Motorcycle : inherits Vehicle <|-- Truck : inherits Vehicle *-- Engine : has-a

Purpose: Show class relationships using Unified Modelling Language (UML). Triangles (▲) represent inheritance (is-a), diamonds represent composition (has-a). Private attributes (-) and public methods (+) are documented.
Syllabus Link: SE-11-02, SE-11-08
Try This: Create a class diagram for an animal system (Animal → Dog, Cat, Bird) or a gaming system (Character → Warrior, Mage, Archer).


📦 System Modelling Tools

🎯 (SE-11-01, SE-11-06)

📌 Use data flow diagrams, structure charts and class diagrams to represent a system. These modelling tools allow engineers to visualise system architecture before implementation.

  • 🛠️ Class Diagrams (p. 9): The primary tool for OOP. It represents classes as three-part boxes (Name, Attributes, Methods) and uses solid lines with hollow arrows to show inheritance (Outcome SE-11-06).
  • 🛠️ Data Flow Diagrams (p. 5): Useful for showing how data moves between objects and external entities, focusing on the "transformation" of data (Outcome SE-11-06).
  • 🛠️ Structure Charts (p. 7): While often procedural, they are used in OOP to map out complex method calls and parameter passing between modules (Outcome SE-11-06).

📐 Structure Chart — Student Management System

Top-down decomposition of modules and function calls

A structure chart shows how a program is broken into modules (functions/methods) and how they call each other. Unlike a class diagram, it focuses on the hierarchy of calls, not on OOP relationships. Parameters passed between modules are shown on the connecting arrows.

Structure Chart: Student Management System
==========================================
              [Main Program]
              /            \
   [Student Module]    [Report Module]
    /     |     \           |      \
[Add] [Update] [Delete]  [View]  [Export]
  |       |
[Validate] [Save to DB]

Structure Chart vs Class Diagram — What's the difference?

  • Structure Chart: Shows how a program is broken into modules/functions and the hierarchy of calls between them. It is procedural in nature — "what calls what".
  • Class Diagram: Shows OOP relationships between classes — inheritance (IS-A), composition (HAS-A), and associations. It models objects and their data, not just call sequences.
  • In an OOP project, you may use a structure chart to plan the top-level flow (e.g., which module runs first) and a class diagram to design the objects within each module.

Online Tool Suggestion: Students should use Lucidchart, Draw.io, or Mermaid.js to create professional UML Class Diagrams and DFDs.


🎨 Design Process for OOP

🎯 (SE-11-01)

📌 Describe the process of design used to develop code in an OOP language, including task definition, top-down and bottom-up approaches, facade pattern, and agility.

📝 Task Definition

The initial phase where engineers formally outline the problem scope and define the specific objects and classes required to solve it. This involves understanding the requirements, identifying the key entities (objects) in the problem domain, and determining their relationships and responsibilities.

↕️ Top-down and Bottom-up Design

Top-down design starts with high-level system requirements and breaks them down into smaller sub-systems or classes. This approach defines the overall structure first, then fills in details for each class. Bottom-up design involves building and testing robust base classes first, then combining them to create the final application. Effective OOP engineering often uses a hybrid approach, ensuring individual objects are functional (bottom-up) while meeting the overall system architecture (top-down).

🖼️ Facade Pattern

The facade pattern is a design technique that provides a simplified "front-end" interface to a complex set of classes, hiding the internal complexity from the user. By creating a single facade class that coordinates multiple internal classes, developers make the system easier to use, understand, and maintain. The user interacts only with the facade, not the complex subsystems behind it.

🌏 Example: A WebServerFacade that simplifies accessing a complex library system. Instead of manually handling authentication, database queries, caching, and logging:

Python — Facade Pattern Example
# Complex internal classes (hidden from user)
class AuthenticationService: # Handles user login
    def authenticate(self, username, password): pass

class DatabaseService: # Queries the database
    def query(self, sql): pass

class CacheService: # Stores frequently accessed data
    def get(self, key): pass

# FACADE: Single simple interface
class LibraryFacade:
    def __init__(self):
        self.auth = AuthenticationService()
        self.db = DatabaseService()
        self.cache = CacheService()

    def get_book_info(self, username, book_id):
        # User calls ONE method; facade handles everything inside
        if not self.auth.authenticate(username):
            return None
        if book_data := self.cache.get(book_id):
            return book_data
        return self.db.query(f"SELECT * FROM books WHERE id={book_id}")

# User only sees the simple facade interface
library = LibraryFacade()
book = library.get_book_info("john_doe", 42)

🏃 Agility

Agility in OOP design refers to implementing an iterative development cycle where code is regularly reviewed and updated based on testing, user feedback, and changing requirements. Agile approaches favour frequent refactoring, continuous improvement, and adaptive design rather than rigid upfront planning. This flexibility allows software to evolve as understanding of the problem deepens.


📊 Assessing Code Effectiveness

🎯 (SE-11-07)

📌 Assess the effectiveness of programming code developed to implement an algorithm. This involves evaluating whether the code correctly implements the algorithm, runs efficiently, and is maintainable.

📋 Evaluation Criteria

  • Correctness: Does the code produce the expected output for all valid inputs? Does it correctly implement the algorithm's logic?
  • Efficiency: Does the code run efficiently in terms of time complexity (execution speed) and space complexity (memory usage)? Can it handle large datasets?
  • Maintainability: Is the code well-structured, clearly commented, and easy for other developers to understand and modify? Does it follow OOP principles?
  • Robustness: Does the code handle edge cases and errors gracefully without crashing?
  • Scalability: Can the code be extended with new features or adapted to larger problems without major restructuring?

In OOP, effectiveness is enhanced through proper use of classes, inheritance, and encapsulation. A well-designed class hierarchy makes code more testable, reusable, and maintainable. Poor design—such as overly deep inheritance chains or god objects (classes that do too much)—reduces effectiveness.


📨 Message-Passing Between Objects

🎯 (SE-11-08)

📌 Investigate how OOP languages handle message-passing between objects. In OOP, objects communicate by "passing messages"—which is essentially one object calling a method of another object.

📨 How Message-Passing Works

When Object A calls a method on Object B, Object A is sending a "message" to Object B requesting it to perform a specific action. The syntax looks like objectB.methodName(parameters). This allows for decoupling: Object A needs to know what Object B can do (its interface), but not how it does it (its implementation).

This investigated feature enables several OOP benefits:

  • Modularity: Each object is independent and can be tested, modified, or replaced without affecting other objects that use its interface.
  • Flexibility: Polymorphism allows different objects to respond to the same message in different ways based on their class type.
  • Scalability: New classes can be added without modifying existing code that sends messages through a common interface.
  • Maintainability: Changes to an object's internal implementation don't affect other objects as long as the interface (method signatures) remains the same.
Python — Message-Passing Example
class BankAccount:
    def withdraw(self, amount):
        # Message: "withdraw 500 from this account"
        if amount <= self.balance:
            self.balance -= amount
            return True
        return False

account = BankAccount()
result = account.withdraw(500) # Passing message to account object
# The account handles the message; caller doesn't care how

📨 Sequence Diagram - Message-Passing

Objects communicating through method calls

This sequence diagram shows how objects communicate by passing messages (method calls). The MainClass sends a "createAccount()" message to BankAccount, which initializes state. Then MainClass sends "withdraw(500)" and BankAccount responds with the result. The caller never needs to know how the bank account handles the withdrawal — encapsulation hides implementation details.

sequenceDiagram participant Client as 👤 Client Code participant Account as 🏦 BankAccount
Object participant Validator as ✓ Balance
Validator Client->>Account: withdraw(500)
[Message] Note over Account: receive message
process internally Account->>Validator: check balance >= 500? Validator-->>Account: True/False alt Balance Sufficient Account->>Account: self.balance -= 500 Account-->>Client: return True else Insufficient Balance Account-->>Client: return False end Note over Client,Account: Client doesn't know HOW
balance checking works
Only WHAT the interface does

Purpose: Visualise how objects communicate through method calls (messages) while maintaining encapsulation and loose coupling
Syllabus Link: SE-11-04, SE-11-08
Try This: Design a system where Student sends message to Enrollment to register for a Course, and trace all the internal validations without revealing implementation details


⚡ Code Optimisation in Software Engineering

🎯 (SE-11-08)

📌 Explain code optimisation in software engineering. Code optimisation is the engineering practice of modifying software to make it more efficient, typically by reducing memory usage (RAM) or increasing execution speed (CPU).

⚡ Optimisation Techniques in OOP

  • Algorithm Refinement: Improving the efficiency of algorithms within methods by choosing faster data structures or reducing iterations.
  • Lazy Instantiation: Creating objects only when needed, rather than upfront, to reduce memory overhead.
  • Inheritance Hierarchy Optimisation: Ensuring inheritance chains are not unnecessarily deep, which can slow method lookups.
  • Caching: Storing results of expensive operations to avoid recalculating them.
  • Code Profiling: Identifying bottlenecks (slow parts) in code using profiling tools to target optimisation efforts where they matter most.

Optimised code performs better on target hardware, consumes fewer resources, and provides a better user experience. However, optimisation must be balanced against code readability and maintainability—premature optimisation can make code harder to understand without providing meaningful performance gains.

Python — Clean Implementation
class SystemManager:
    def __init__(self):
        self.active = True # Initialise state

    def run_task(self): # One logical task per subroutine
        if self.active:
            self.__log_event() # Modular call

    def __log_event(self): # Private helper
        pass # Ease of maintenance

🤝 Collaborative Code Development Features

🎯 (SE-11-01, SE-11-06)

📌 Outline the features of OOP that support collaborative code development, including consistency, code commenting, version control, and feedback mechanisms.

  • Consistency: Using classes and standard naming conventions (e.g., CamelCase for classes) ensures the codebase is predictable for all team members.
  • Code Commenting: Professional documentation within classes (Docstrings) explains the purpose of methods to other developers.
  • Version Control: Tools like Git allow multiple engineers to work on different classes simultaneously and merge their work into a single repository (Outcome SE-11-06).
  • Feedback: Peer reviews of class structures and logic ensure that code is modular, reusable, and free of logic errors.

Syllabus Part 2 💻 Programming in OOP

💻 Design and Implementation in OOP

🎯 (SE-11-05)

📌 Design and Implement computer programs involving branching, iteration and functions in an OOP language for an identified need or opportunity.

🏗️ Program Structure with Control Flow

Effective OOP programs combine three fundamental control structures:

  • Branching: Using if/else and switch statements to make decisions based on object state or input. For example, a BankAccount.withdraw() method checks if sufficient balance exists.
  • Iteration: Using for, while, and foreach loops to process collections of objects or repeat operations. For example, iterating through a list of Customer objects.
  • Functions (Methods): Encapsulating logic in methods that accept parameters and return results. Each method should perform one logical task.

🌍 Real-World Application

When designing OOP programs for actual needs, engineers must:

  • Identify the objects in the problem domain (e.g., Student, Course, Enrolment for a school system).
  • Define classes with appropriate attributes and methods.
  • Design relationships between classes (inheritance, composition).
  • Implement logic using branching and iteration within methods.
  • Test each class independently before integration.

🔧 Implementation and Modification Practices

🎯 (SE-11-05, SE-11-06)

📌 Implement and Modify OOP programming code following best practices for maintainability, testability, and collaboration.

✨ Clear and Uncluttered Mainline

The main program should be clean and easy to read, delegating complex logic to helper methods. Avoid embedding business logic directly in the main method. Instead, create objects and call their methods. This separation of concerns makes the flow of the program obvious and maintainable.

🎯 One Logical Task Per Subroutine

Each method should do one thing and do it well (the Single Responsibility Principle). A method named processPayment() should handle payment logic only, not also send emails and update inventory. This principle makes code easier to understand, test, and reuse.

🧱 Use of Stubs

A stub is a minimal implementation or placeholder for a subroutine, method, or class that will be fully developed later. Stubs allow high-level logic to be tested before all detailed methods are complete. For example, a DatabaseConnection.save() stub might just print "Saving..." while development of the actual database code continues elsewhere. This enables parallel development and testing.

🔀 Control Structures and Data Structures

Implementation must effectively use both control structures (if/else, loops) to direct program flow and data structures (arrays, lists, trees) to organise data. Choosing the right combination improves both efficiency and code clarity.

🛠️ Ease of Maintenance

Code should be maintainable through:

  • Clear naming conventions for classes, methods, and variables.
  • Proper encapsulation—hiding implementation details behind clear interfaces.
  • Comprehensive comments explaining non-obvious logic.
  • Avoiding deep nesting and overly complex methods.
  • Following consistent formatting and style.

📜 Version Control

Using version control systems (Git, GitLab, Bitbucket) is essential for OOP development. Version control allows multiple developers to work on different classes simultaneously, track changes, revert to previous versions if needed, and maintain an audit trail of who changed what and why. Commits should be frequent and have clear messages describing the changes made.

💾 Regular Backup

Beyond version control, regular backups of the entire codebase ensure protection against data loss from hardware failures. Backups should be stored off-site or in the cloud to protect against local disasters. Automated backup systems are preferred to ensure consistency and reliability.


🧪 Testing and Quality Assurance

🎯 (SE-11-06, SE-11-07)

📌 Apply methodologies to test and evaluate code. Quality assurance ensures software meets specified engineering standards and works correctly.

🪜 Testing Hierarchy: Unit, Subsystem, and System

Testing occurs at multiple levels, building from smallest to largest components:

🔬 Unit Testing

Testing individual classes or methods in isolation to ensure they perform their intended function correctly. For example, testing a BankAccount.withdraw() method with various inputs (sufficient balance, insufficient balance, negative amount, zero amount). Unit tests are the most granular and should be written by the developer who wrote the code.

🔗 Subsystem Testing

Testing groups of related classes that work together as a functional subsystem. For example, testing the interaction between Customer, Order, and Payment classes to ensure orders are created and payments are processed correctly. This level catches integration issues that unit tests might miss.

✅ System Testing

Testing the entire integrated application end-to-end to ensure all components work together and meet the overall requirements. System testing includes testing the application's interaction with external systems (databases, APIs, etc.) and verifying that the entire workflow functions as expected.

📦 Testing Approaches: Black, White, and Grey Box

⬛ Black Box Testing

Testing inputs and outputs without access to or knowledge of the internal code structure. The tester treats the software as a "black box" and tests it purely based on specifications. This approach ensures the software meets functional requirements from a user's perspective. For example, testing that a login system correctly accepts valid credentials and rejects invalid ones, without examining the authentication code.

⬜ White Box Testing

Testing with complete access to and understanding of the internal code structure. The tester examines the code logic, control flow, and data flow to ensure all paths and conditions are tested. This approach is critical for security, ensuring all potential vulnerabilities and logic errors are identified. For example, verifying that all conditional branches (IF/ELSE statements) in a method execute correctly under different conditions.

🔷 Grey Box Testing

A hybrid approach where the tester has partial knowledge of the internal structure (like knowing which classes exist and their general purpose) but not the implementation details. This allows more targeted testing than black box while being less resource-intensive than white box. For example, knowing the database schema but not the exact SQL queries used.

✅ Quality Assurance Process

Quality Assurance (QA) is the systematic process of ensuring software meets specified engineering standards and quality criteria. QA includes:

  • Requirements Verification: Ensuring the code implements all specified requirements.
  • Code Review: Peer examination of code for logic errors, adherence to standards, and maintainability.
  • Automated Testing: Using test suites that run automatically on every code change to catch regressions.
  • Documentation: Ensuring code is properly documented so maintainability is assured.
  • Performance Testing: Verifying the code meets performance requirements under expected and peak loads.
  • Security Testing: Identifying and fixing security vulnerabilities (part of Secure Software Development as per SE-12-X topics).

📚 Practical HSC Project OOP Design

🎯 (SE-11-01, SE-11-02, SE-11-05, SE-11-06)

📌 Apply OOP design principles to a realistic HSC project scenario. The Library Management System is a classic example that demonstrates all four OOP pillars in a coherent, assessable design.

📖 Library Management System — Class Diagram

A Library Management System is an ideal HSC project because it has multiple entity types with clear IS-A and HAS-A relationships. The diagram below shows how Book, Member, and Library interact:

📖 Class Diagram — Library Management System

UML class diagram showing relationships between core entities

This class diagram models a Library Management System with three core classes. Library acts as the central coordinator (facade), holding collections of Books and Members. Members can borrow multiple Books, creating a many-to-many relationship managed through the Library. Private attributes (prefixed with -) are accessed only via public methods (+), demonstrating encapsulation.

classDiagram class Book { -String isbn -String title -String author -bool available +checkout() bool +return_book() void +get_details() dict } class Member { -int member_id -String name -String email -list borrowed_books +borrow(book) bool +return_book(book) void +get_history() list } class Library { -list books -list members +add_book(book) void +register_member(member) void +search(query) list } Library "1" --> "many" Book Library "1" --> "many" Member Member "1" --> "many" Book : borrows

Purpose: Demonstrate a complete OOP design with composition (Library HAS-A Books/Members) and association (Member borrows Books)
Syllabus Link: SE-11-02, SE-11-06
Try This: Extend this diagram by adding an EBook class that inherits from Book and adds a file_format attribute.

🐍 Python Implementation — Book and EBook Classes

The following implementation demonstrates all four OOP pillars. Note how encapsulation is achieved with private attributes (double underscore prefix), abstraction through the property decorator, inheritance via EBook(Book), and polymorphism by overriding checkout():

Python — Library System OOP Implementation
class Book:
    def __init__(self, isbn: str, title: str, author: str):
        self.__isbn = isbn          # Private attribute
        self.__title = title
        self.__author = author
        self.__available = True

    @property
    def title(self):               # Getter (encapsulation)
        return self.__title

    @property
    def available(self):
        return self.__available

    def checkout(self) -> bool:
        if self.__available:
            self.__available = False
            return True
        return False

    def return_book(self) -> None:
        self.__available = True

    def __str__(self):             # String representation
        status = "Available" if self.__available else "Checked out"
        return f"{self.__title} by {self.__author} [{status}]"


class EBook(Book):                 # Inheritance
    def __init__(self, isbn, title, author, file_format: str):
        super().__init__(isbn, title, author)
        self.__file_format = file_format

    def download(self) -> str:     # Polymorphism - new behaviour
        return f"Downloading {self.title} as {self.__file_format}..."

    def checkout(self) -> bool:    # Polymorphism - override
        # EBooks can always be checked out (no physical limit)
        return True

OOP Design Checklist — Use This for Your HSC Project

  • Have you identified all objects/entities in your system?
  • Are attributes private with getters/setters where needed?
  • Is there appropriate use of inheritance (IS-A relationship)?
  • Is composition used for HAS-A relationships?
  • Are method names clear and do they follow single responsibility?
  • Have you drawn a UML class diagram in your documentation?

When NOT to Use OOP

  • Simple scripts (e.g., a one-off data conversion) don't need classes — plain functions are simpler and faster to write.
  • OOP shines when you have multiple similar objects that share structure but differ in state or behaviour (e.g., many Book objects in a library).
  • Data-heavy transformations (e.g., mathematical pipelines, batch processing) may suit a functional or procedural style more naturally.
  • For your HSC project: if your system involves 3 or more entity types that each have their own data and behaviour, OOP is the right choice and will earn better marks for design quality.

Part 3 🏗️ Advanced OOP

🏗️ Design Patterns

🎯 (SE-11-02, SE-11-06, SE-11-07)

📌 Apply established design patterns to solve recurring software design problems, producing more maintainable, flexible, and scalable solutions.

Design patterns are reusable solutions to commonly occurring problems in software design. They are not code you copy — they are templates or blueprints for how to structure your code.

👤 Singleton Pattern

Intent: Ensure that a class has only one instance and provide a global access point to it.

Use cases: Database connections (one shared pool), configuration managers (one settings object), logging systems (one log file handler).

Python — Singleton using __new__
class DatabaseConnection:
    _instance = None   # Class-level variable holds the single instance

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.connected = False
        return cls._instance

    def connect(self, host):
        self.connected = True
        self.host = host
        print(f"Connected to {host}")

# Usage — both variables point to the SAME object
db1 = DatabaseConnection()
db2 = DatabaseConnection()
db1.connect("localhost")
print(db1 is db2)          # True — same instance
print(db2.host)            # "localhost" — shared state
Use Singleton sparingly. It introduces global state, which makes unit testing harder (tests affect each other through the shared instance). Prefer dependency injection when possible.

🔔 Observer Pattern

Intent: Define a one-to-many dependency so that when one object changes state, all its dependents are notified automatically.

Real-world analogy: A newsletter — subscribers (observers) register to receive updates from a publisher (subject). When the publisher posts a new article, all subscribers are notified.

Observer Pattern Structure
──────────────────────────────────────────────
Subject (Publisher)
  ├── attach(observer)    ← add a subscriber
  ├── detach(observer)    ← remove a subscriber
  └── notify()           ← calls update() on all observers

ConcreteObserver
  └── update(data)       ← receives the notification
──────────────────────────────────────────────
Python — Observer Pattern
class Subject:
    def __init__(self):
        self._observers = []

    def attach(self, observer):
        self._observers.append(observer)

    def detach(self, observer):
        self._observers.remove(observer)

    def notify(self, data):
        for observer in self._observers:
            observer.update(data)

class EmailAlert:
    def update(self, data):
        print(f"EMAIL: {data}")

class SMSAlert:
    def update(self, data):
        print(f"SMS: {data}")

# Usage
stock = Subject()
stock.attach(EmailAlert())
stock.attach(SMSAlert())
stock.notify("Price dropped below $50!")
# EMAIL: Price dropped below $50!
# SMS: Price dropped below $50!

🖼️ MVC Pattern (Model-View-Controller)

Intent: Separate an application into three interconnected layers to organise code and enable independent development/testing of each layer.

MVC Data Flow
─────────────────────────────────────────────────────
User Action
    │
    ▼
  View  ──────(user input)──────►  Controller
                                       │
                              (updates/queries)
                                       │
                                       ▼
                                    Model  (data + business logic)
                                       │
                              (returns data)
                                       │
                                       ▼
  View  ◄────(updates display)──── Controller
─────────────────────────────────────────────────────
Python — MVC for Student Grades
# MODEL — data and business logic
class GradeModel:
    def __init__(self):
        self._grades = {}

    def add_grade(self, student, score):
        self._grades[student] = score

    def get_average(self):
        if not self._grades:
            return 0
        return sum(self._grades.values()) / len(self._grades)

    def get_grades(self):
        return dict(self._grades)

# VIEW — display only, no logic
class GradeView:
    def show_grades(self, grades):
        print("\n--- Grade Report ---")
        for student, score in grades.items():
            print(f"  {student}: {score}")

    def show_average(self, avg):
        print(f"  Class Average: {avg:.1f}")

# CONTROLLER — coordinates model and view
class GradeController:
    def __init__(self):
        self._model = GradeModel()
        self._view = GradeView()

    def add_result(self, student, score):
        self._model.add_grade(student, score)

    def show_report(self):
        self._view.show_grades(self._model.get_grades())
        self._view.show_average(self._model.get_average())

# Usage
app = GradeController()
app.add_result("Alice", 88)
app.add_result("Bob", 75)
app.add_result("Carol", 92)
app.show_report()

📊 Design Pattern Comparison

PatternIntentKey BenefitHSC Use Case
FacadeSimplify a complex subsystemEasier API for complex codeWrapping database or file operations
SingletonOne instance onlyShared global resourceConfig manager, DB connection
ObserverNotify many from oneDecoupled event systemGUI events, notifications
MVCSeparate data/UI/logicIndependent testing of each layerWeb apps, GUI applications

🔷 Abstract Classes & Interfaces

🎯 (SE-11-02, SE-11-06)

📌 Apply abstract classes to define common interfaces for a group of related classes, enforcing consistent method signatures across subclasses.

🔷 What is an Abstract Class?

An abstract class is a class that cannot be instantiated directly — it exists purely to be subclassed. It may define abstract methods (declared but not implemented) that every subclass must override. This enforces a consistent interface across all subclasses.

Python — Abstract Class using abc module
from abc import ABC, abstractmethod
import math

# Abstract base class — cannot be instantiated directly
class Shape(ABC):

    @abstractmethod
    def area(self) -> float:
        """Every subclass MUST implement this method."""
        pass

    @abstractmethod
    def perimeter(self) -> float:
        """Every subclass MUST implement this method."""
        pass

    def describe(self):   # Concrete method — shared by all subclasses
        print(f"Area: {self.area():.2f}, Perimeter: {self.perimeter():.2f}")

# Concrete subclass — must implement area() and perimeter()
class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return math.pi * self.radius ** 2

    def perimeter(self):
        return 2 * math.pi * self.radius

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)

# Usage
shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
    shape.describe()

# shape = Shape()   # ← TypeError! Cannot instantiate abstract class

⚖️ Abstract Class vs Concrete Class

FeatureAbstract ClassConcrete Class
Can be instantiated?NoYes
Can have abstract methods?YesNo
Can have concrete methods?YesYes
PurposeDefine a common interface/templateProvide a full implementation
Python syntaxclass X(ABC): with @abstractmethodclass X: or class X(Parent):

🦆 Duck Typing in Python

"If it walks like a duck and quacks like a duck, it's a duck." Python does not enforce strict type hierarchies — any object with the right methods will work, regardless of its class.

Python — 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 make_noise(animal):
    print(animal.speak())   # Works for ANY object with a speak() method

for creature in [Dog(), Cat(), Robot()]:
    make_noise(creature)

⚠️ Exception Handling & Custom Exceptions

🎯 (SE-11-05, SE-11-06)

📌 Apply exception handling to build robust programs that gracefully manage unexpected conditions without crashing.

⚠️ try / except / else / finally

Python — Full exception handling structure
try:
    age = int(input("Enter age: "))   # might raise ValueError
    result = 100 / age                # might raise ZeroDivisionError
except ValueError:
    print("Error: please enter a whole number.")
except ZeroDivisionError:
    print("Error: age cannot be zero.")
except Exception as e:
    print(f"Unexpected error: {e}")   # catch-all for anything else
else:
    print(f"Result: {result:.2f}")    # runs ONLY if no exception occurred
finally:
    print("Done.")                    # ALWAYS runs, exception or not

🛠️ Custom Exception Classes

You can define your own exception classes by inheriting from Exception. This lets you raise domain-specific errors with meaningful names.

Python — Custom exceptions in a BankAccount class
class InsufficientFundsError(Exception):
    def __init__(self, amount, balance):
        super().__init__(f"Cannot withdraw ${amount:.2f}: only ${balance:.2f} available.")
        self.amount = amount
        self.balance = balance

class InvalidAmountError(Exception):
    pass

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

    def deposit(self, amount):
        if amount <= 0:
            raise InvalidAmountError("Deposit amount must be positive.")
        self._balance += amount

    def withdraw(self, amount):
        if amount <= 0:
            raise InvalidAmountError("Withdrawal amount must be positive.")
        if amount > self._balance:
            raise InsufficientFundsError(amount, self._balance)
        self._balance -= amount

    @property
    def balance(self):
        return self._balance

# Usage
account = BankAccount("Alice", 100)
try:
    account.withdraw(150)
except InsufficientFundsError as e:
    print(e)   # Cannot withdraw $150.00: only $100.00 available.

🪜 Exception Hierarchy (text diagram)

BaseException
  ├── SystemExit            ← raised by sys.exit()
  ├── KeyboardInterrupt     ← Ctrl+C
  └── Exception             ← base for all "normal" exceptions
        ├── ValueError      ← wrong type/value (int("abc"))
        ├── TypeError       ← wrong type operation (1 + "a")
        ├── IndexError      ← list index out of range
        ├── KeyError        ← dict key not found
        ├── FileNotFoundError
        ├── ZeroDivisionError
        ├── AttributeError  ← object has no such attribute
        └── (your custom exceptions inherit from Exception)
Best Practices:
✓ Catch specific exceptions, not bare except: — bare except hides bugs.
✓ Use finally for cleanup code (closing files, releasing locks).
✓ Raise exceptions with helpful messages explaining the problem.
✓ Don't use exceptions for normal flow control — use if/else for expected conditions.