Object-Oriented Programming Concepts (AQA A-Level Computer Science): Revision Notes
Object-Oriented Programming Concepts
Introduction to object-oriented programming
Object-oriented programming (OOP) represents a way of organising code that mirrors how things work in the real world. It builds upon the structured programming approach you learned in earlier chapters, where programs are broken down into modules, subroutines and functions. OOP takes this modular concept further by grouping related data and the code that works with that data into single units called objects.
The fundamental difference between procedural and object-oriented programming lies in how code and data are organised. In procedural programming, your code and the data it operates on are stored separately. With OOP, everything is packaged together within objects, and you can control precisely how the data can be manipulated through defined methods.
Real-World Banking System Example
Think about a real-world banking system. In real life, a bank contains various objects such as customers and financial transactions. Within each of these, there are specific pieces of data and particular ways of handling that data. For instance, customer data needs to be processed in specific ways - you might need to add new customer details or calculate how much money to withdraw from an account balance.
When creating an object-oriented banking application, you would structure it to reflect these real-life relationships. You might create one object to handle customers and another for transactions. The customer object would contain all the customer data along with all the processes (called methods) needed to work with that data.
Advantages of object-oriented programming
This approach offers several significant benefits:
- Programs become much easier to modify because changes only need to be made to specific modules rather than throughout the entire codebase.
- Adding new functionality is straightforward - you simply add a new module rather than restructuring existing code.
- The modular design approach allows teams of programmers to work independently on self-contained modules, making collaboration more efficient.
- Objects can inherit attributes and behaviours, making code reusable throughout the program.
- Changes to data happen within objects rather than across the whole program, reducing the likelihood of bugs caused by unintended side effects.
- Libraries can be created easily, allowing code to be reused across different projects.
Key Advantage: Maintainability
The most significant benefit of OOP is maintainability. Because code and data are encapsulated within objects, modifications can be made to specific modules without affecting the entire program. This dramatically reduces the risk of introducing bugs and makes programs easier to update and extend.
Encapsulation
Encapsulation is one of the most important principles in object-oriented programming. It refers to the concept of keeping both the data and the code that works with that data together within the same object. The code within an object is organised into methods, which are essentially subroutines designed to perform particular tasks on the data stored in the object.

This principle means that objects become self-contained units. In theory, this creates far fewer side effects compared to procedural programming languages. A side effect occurs when a subroutine modifies the value of a variable that then affects other parts of the program in unexpected ways.
Information Hiding and Data Protection
With proper encapsulation, data can only be directly manipulated by the methods contained within the object itself. This concept is sometimes called information hiding, because the data becomes directly available only to the object that actually needs to use it. Code outside the class can only access the data through the defined methods, which act as controlled gateways.
Properties and methods
When working with objects, you'll encounter two key terms:
Properties define the characteristics of an object or class in terms of its data. They describe what information the object holds.
Methods are the code routines contained within a class. They define what the object can do and how it can manipulate its data.
Classes and objects
The two main building blocks of any object-oriented program are classes and objects. Understanding the relationship between these two concepts is essential.
A class serves as a blueprint or master template that defines a related group of things. It specifies what properties (data) the objects will have and what methods (behaviours) they can perform. However, the class itself doesn't store any actual data - it just defines the structure.
Objects are created from classes. Each object represents a specific instance of a class, meaning it has the same properties and methods defined by its class, but it contains its own unique data values that the methods can work with.
Understanding the Class-Object Relationship
Think of a class as a cookie cutter and objects as the cookies. The cookie cutter (class) defines the shape and structure, but each cookie (object) is a separate, individual item that exists independently with its own characteristics.
Banking example
Consider a banking system to see how this works in practice:
Worked Example: Banking System Class Structure
A class might be called Account, which defines the properties and methods that any bank account should have. All accounts share similar properties such as an account number and current balance. They also share the same methods - all accounts need to be able to check their balance and add interest.
Different types of accounts would be subclasses of the main Account class:
- The Current account might have specific properties like overdraft limits and payment methods.
- The Mortgage account might have properties such as an end date when the mortgage is fully paid.
Each subclass inherits all the properties and methods from the Account class, but adds its own unique features. Objects are then created from these classes to represent individual customer accounts. For example, an object created from the Current class would represent one person's specific current account with their actual balance and account number.
Creating classes in Python
Here's how you define a basic Account class in Python:
class Account():
def __init__(self, accountNumber, openingDate,
currentBalance, interestRate):
self.accountNumber = accountNumber
self.openingDate = openingDate
self.currentBalance = currentBalance
self.interestRate = interestRate
def getAccountNumber(self):
return self.accountNumber
def getCurrentBalance(self):
return self.currentBalance
def addInterest(self):
interest = self.currentBalance * self.interestRate
self.currentBalance += interest
def setInterestRate(self, interestRate):
self.interestRate = interestRate
Understanding the Code
This code creates a base class with properties (accountNumber, openingDate, currentBalance, interestRate) and methods that can work with those properties. The init method is a special constructor that initialises new objects with their starting values. Methods like getAccountNumber and addInterest define how the object's data can be accessed and manipulated.
Inheritance
Inheritance in object-oriented programming works similarly to biological inheritance. You start with a set of characteristics and then add to what already exists. New features and abilities can be added, but you cannot delete what's already been inherited.

Building on the bank account example, you might start with a base class called Account containing common properties such as:
- AccountNumber: String
- DateOpened: Date
- CurrentBalance: Currency
- InterestRate: Real
When programmed, these properties become variables with appropriate data types assigned.
The class would also include common methods such as:
- AddInterest
- GetCurrentBalance
- GetInterestRate
These methods become the subroutines (procedures or functions) required to work with the account data. For instance, a procedure would be defined to calculate the amount of interest to add to the account.
Inheritance is Additive Only
The properties and methods defined in the base class are available to all types of accounts. Whether it's a current account or mortgage account, they all share these common characteristics. However, different account types need additional properties and methods specific to their purpose, so these are set up as subclasses.
Remember: You can add new features through inheritance, but you cannot remove inherited properties or methods.
For example, a current account might have a property called Overdraft, indicating whether the customer has an overdraft facility. This is unique to current accounts and wouldn't appear in a mortgage account. A method called setOverdraft could be defined in the subclass to manage this property.
Similarly, a mortgage account might have a property called EndDate, representing when the mortgage will be fully paid off. This isn't relevant to current accounts, so it belongs in the Mortgage subclass. A method called GetEndDate could identify when the final payment is due.
Inheritance hierarchy
This relationship between classes creates a hierarchical structure:


The Account class is described as a base class, super class or parent class - it's the main class from which other classes inherit. The Current and Mortgage classes are called subclasses, derived classes or child classes. This example uses just two types of accounts, but the same principle can extend to create further subclasses, such as savings accounts, trust fund accounts, and so on.
Implementing inheritance in Python
Here's how the Current account subclass inherits from Account:
class Current(Account):
def __init__(self, accountNumber, openingDate,
currentBalance, interestRate, paymentType,
overdraft):
Account.__init__(self, accountNumber,
openingDate, currentBalance, interestRate)
self.paymentType = paymentType
self.overdraft = overdraft
def setPaymentType(self, paymentType):
self.paymentType = paymentType
def setOverdraft(self, overdraft):
self.overdraft = overdraft
def getOverdraft(self):
return self.overdraft
Notice how Current(Account) indicates that Current inherits from Account. The Account.init call ensures that all the base class properties are initialised before adding the new properties specific to current accounts (paymentType and overdraft).
The Mortgage subclass would be implemented similarly:
class Mortgage(Account):
def __init__(self, accountNumber, openingDate,
currentBalance, interestRate, endDate):
Account.__init__(self, accountNumber,
openingDate, currentBalance, interestRate)
self.endDate = endDate
def getEndDate(self):
return self.endDate
def setEndDate(self, endDate):
self.endDate = endDate
Class diagrams for inheritance
Class diagrams provide a standard visual method for representing classes, their properties and methods, and the relationships between them. They're particularly useful for showing inheritance structures.
Class Diagram Conventions
When representing inheritance, class diagrams follow these conventions:
- They display a hierarchical structure with the base class at the top and subclasses shown below.
- A subclass inherits all properties and methods from the base class.
- Arrows point upward to show the direction of inheritance, from subclass to superclass.
- Each class is represented by a box divided into three sections: the class name at the top, properties in the middle, and methods at the bottom.
The diagram uses special notation to indicate visibility:
- The
+symbol means the properties and methods are public and accessible to all classes. - The
-symbol indicates private properties and methods that can only be used within that specific class. - The
#symbol shows protected properties and methods that can be used in the class and any of its subclasses.
Data types are also specified for each variable in the diagram, making it clear what kind of data each property will hold.
Instantiation
Instantiation is the process of creating an actual object from a class definition. In other words, you're creating a real instance of an object using the properties and methods described in the class blueprint.
With the Account example, many different objects can be created from the class definition. Each object would represent a specific customer's current account, containing their unique data. The programmer needs to go through the instantiation process for every object required in the program.
The Constructor's Role
When programming, a special subroutine called a constructor is invoked when an object is instantiated. The constructor initialises the object with starting values.
Here's an example:
Worked Example: Creating an Account Object
new_account = Account(41344987, date.today(), 374.34, 0.032)
This line creates a new account object with specific values:
- Account number: 41344987
- Opening date: today's date
- Balance: $374.34
- Interest rate: 0.032 (3.2%)
The object new_account now exists in memory and can be used to access the account's properties and methods.
Polymorphism and overriding
The term polymorphism literally means "to take on many shapes". In object-oriented programming, it describes a situation where a method inherited from a base class can be redefined and used in different ways, depending on the data in the subclass that inherited it.
Polymorphism relates to class hierarchies and how classes inherit properties from other classes. Consider a common method that needs to be used throughout your program. Since this method is critical to the program, it could be defined in a base class and then inherited by other classes. However, the data in these new classes might be of a different type. Rather than having to define a completely new method, polymorphism allows the original method to be redefined to work with the new data.
Example: calculating interest
Imagine we define a method to calculate interest payments on an account. This method would be stored in the base Account class and then inherited by the Current and Mortgage subclasses. The data needed for interest calculations might differ - for instance, a current account might use a higher interest rate or calculate over a different time period. Each subclass can define a slightly different method to calculate interest, whilst keeping the same method name as in the base class.
Understanding Overriding
When the subclass implements its own version of the method, this is called overriding because it overrides (or replaces) the method in the base class.
Here's an example showing how the Current class might override the addInterest method:
Worked Example: Overriding the addInterest Method
def addInterest(self):
# new method added to the Current class overriding
# the addInterest method in the Account class
# if the account has an overdraft, interest is
# charged on the debt at 5%
if self.overdraft:
charges = self.currentBalance * 0.05
self.currentBalance += charges
# otherwise interest is applied in the same way
# as the superclass (Account)
else:
Account.addInterest(self)
This code checks if the account has an overdraft facility. If it does, interest is calculated differently (at 5% on the debt). If not, it uses the original method from the Account superclass by calling Account.addInterest(self).
Abstract, virtual and static methods
Object-oriented languages handle methods in three different ways, allowing you flexibility in how code is organised and executed. Understanding these different types helps you structure your programs effectively.
Static methods can be used without needing to create an object instance of the class. This is useful for utility functions that don't need to work with specific object data.
Virtual methods are defined in the base class but can be overridden by methods in subclasses where they'll actually be used. This is a key feature of polymorphism, allowing the same method name to behave differently in different contexts.
Abstract methods are not fully implemented in the base class. Instead, they must be provided (implemented) in the subclass. The object acts as an interface between the method definition and the actual data. This ensures that all subclasses include certain essential methods whilst allowing flexibility in how they're implemented.
Choosing the Right Method Type
Which type you use depends on the nature of the methods and data in your class, and when you want the methods to execute in your program. Consider whether the method needs object-specific data (use virtual or abstract) or can work independently (use static).
Aggregation
Aggregation is a method of creating new objects that contain existing objects, based on how objects relate to each other in real life. Object-oriented programming aims to recreate real-world relationships as programming objects.
In real life, objects are related to one another. For example, in a business you might have an object representing the workforce and another representing job roles. Within the workforce, there may be further objects for managers and employees. All these objects exist independently, but there are also relationships between them.
Consider these relationships:
- Managers and employees make up the workforce.
- Job roles can be undertaken by managers or employees.
- Job roles define what managers and employees do as part of the workforce.
Composition aggregation
Composition aggregation (or simply composition) occurs when one object is composed of or made up of two or more existing objects. The key characteristic is that the contained objects cannot exist if the containing object is destroyed.

Worked Example: Workforce Composition
In our example, Workforce is composed of Manager and Employee objects. If you deleted the Workforce object, you would effectively be deleting both Manager and Employee objects.
The diagram shows this with a specific arrow style - notice the solid diamond shape, which indicates that Manager and Employee cannot exist independently of Workforce.
Association aggregation
Association aggregation creates a different kind of relationship. Here, an object is made up of one or more other objects, but those component objects are not entirely dependent on the container for their existence.

You could extend the Workforce object to include a Job Role object. There's a relationship between managers, employees and their job roles - they all have specific roles they carry out. However, if you deleted Job Role, the Manager and Employee objects would still exist as usable objects within the Workforce. This reflects the real world, where a job role isn't fixed - any manager or employee could be assigned any job role.
Composition vs Association: Key Difference
The diagram uses an open (hollow) diamond arrow head, indicating that Manager and Employee can still exist even if Job Role doesn't.
- Composition (solid diamond): Contained objects cannot exist without the container
- Association (hollow diamond): Contained objects can exist independently
Design principles
Three key design principles are recognised as producing the most elegant and maintainable solutions when programming with object-oriented languages.
Encapsulate what varies
This principle relates directly to the concepts of encapsulation and information hiding. The basic idea is that everything that varies should be organised into its own class. Properties and methods should be subdivided into as many classes as needed to accurately reflect the real-life scenario you're modelling.
Worked Example: Subdividing Account Classes
Using the accounts program as an example, you could have an Account base class followed by Current and Mortgage subclasses. However, further analysis might reveal that you need additional subclasses under Current for different types of current accounts, such as:
- Standard
- Premium
- Student
- Child
Each type might have unique properties and methods. You would continue this process until every unique set of properties and methods had its own class.
Favour composition over inheritance
This principle addresses how classes and objects should be instantiated (created). Objects can be created from a base class and inherit its properties and methods. Alternatively, you can use aggregation or composition to combine existing objects into new ones.
Why Favour Composition?
The composition method is generally less error-prone and enables simpler maintenance. For example, with the Workforce scenario, rather than creating a new instance of Workforce from scratch each time, you could create Manager and Employee objects separately and then combine them.
If both Manager and Employee are created correctly, there should be no errors in Workforce. Similarly, any changes made to Manager or Employee will automatically be reflected in Workforce.
Program to interfaces, not implementation
In object-oriented programming, an interface defines methods that classes must implement. An interface specifies what methods should exist but not how they should be coded - that's left to the class implementation.
Think of an interface as an abstract type that gets implemented when a class is created. When a class adheres to the methods defined in an interface, it becomes an implementation of that interface.
Using Interfaces for Flexibility
Using the accounts example, being able to calculate interest and check the balance would be two methods that all accounts must support, regardless of which specific account class they belong to. These would be required by the interface. The way these operations are carried out would be defined within any class that implements the interface, ensuring they're included.
Programs can then be written based on the interfaces rather than individual class implementations. Using this methodology, if classes need to be added or modified, this can be done with reference to the interface. As long as the new or modified class still implements the required interface methods, there will be minimal impact on other parts of the program.
Key Points to Remember:
-
Object-oriented programming organises code in a way that reflects real-world relationships, grouping data and methods together in objects.
-
Encapsulation keeps data and code within the same object, reducing side effects and making programs more maintainable.
-
Classes serve as blueprints that define properties and methods, whilst objects are specific instances created from those classes containing actual data.
-
Inheritance allows subclasses to inherit properties and methods from base classes, creating hierarchical structures that promote code reuse.
-
Polymorphism enables methods inherited from a base class to be redefined in subclasses, with overriding allowing subclasses to replace inherited methods with their own implementations.
-
Aggregation creates relationships between objects through composition (where contained objects cannot exist independently) or association (where they can continue to exist separately).
-
The three key design principles are: encapsulate what varies, favour composition over inheritance, and program to interfaces, not implementation.