Object-oriented programming (OOP) is a programming paradigm centred around the concept of objects.
Table of Contents
What is a Class?
- A class is a blueprint or template for creating objects.
- It defines the attributes(data members) and behaviours(methods) that objects will have.
- Classes are a fundamental concept in object-oriented programming (OOP).
- It’s a real-world entity
For example, if you want to model a Car, you would create a Car class that might have properties like colour, make, and model, and methods (functions) like start_engine() or drive().
# Defining a class
class Car:
def __init__(self, make, model, color):
self.make = make # Attribute
self.model = model # Attribute
self.color = color # Attribute
def start_engine(self): # Method
print(f"The {self.color} {self.make} {self.model}'s engine has started.")
def drive(self): # Method
print(f"The {self.color} {self.make} {self.model} is now driving.")
PythonWhat is an Object?
- Object is an instance of a class.
- All data members and member functions of the class can be accessed with the help of objects.
- When a class is defined, no memory is allocated, but memory is allocated when it is instantiated (i.e. an object is created).
my_car = Car(make="Toyota", model="Camry", color="Red")
# Accessing attributes and methods
print(my_car.make) # Output: Toyota
my_car.start_engine() # Output: The Red Toyota Camry's engine has started.
my_car.drive() # Output: The Red Toyota Camry is now driving.
PythonOOP Concept
Object-oriented programming (OOP) is about classes and objects. OOP enables us to model real-world entities, making code more modular, reusable, and easier to maintain. Four fundamental principles—abstraction, encapsulation, inheritance, and polymorphism—form the backbone of OOP. Understanding these principles is crucial for writing effective and efficient object-oriented code.
Encapsulation
- Wrapping data (attributes) and functions(methods) together in a single unit is called encapsulation.
- It restricts access to certain components of an object to protect the integrity of the data and prevent unauthorized or accidental interference.
- Private Members: In many OOP languages, you can make data members private, so they cannot be accessed from outside the class. For example, in Python, you can prefix an attribute with an underscore (
_
) to indicate it is private by convention only.
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def deposit(self, amount):
self.__balance += amount
def withdraw(self, amount):
if amount <= self.__balance:
self.__balance -= amount
else:
print("Insufficient funds")
def get_balance(self):
return self.__balance
account = BankAccount(1000)
account.deposit(500)
print(account.get_balance()) # Output: 1500
PythonIn the above code, we have wrapped all attributes and methods related to the Bank account as a single unit.
Inheritance
- It allows a new class to inherit properties and behaviour from an existing class.
- It helps in reusing code and establishing a natural hierarchy between classes.
class Vehicle:
def __init__(self, make, model):
self.make = make
self.model = model
def start(self):
return f"{self.make} {self.model} is starting."
class Car(Vehicle):
def __init__(self, make, model, doors):
super().__init__(make, model)
self.doors = doors
def open_doors(self):
return f"Opening {self.doors} doors."
my_car = Car("Honda", "Civic", 4)
print(my_car.start()) # Output: Honda Civic is starting.
print(my_car.open_doors()) # Output: Opening 4 doors.
PythonThe Car doesn’t have method start(), But the Car object is using start() due to inheritance
Polymorphism
- Polymorphism means “many forms” and allows methods to do different things based on the object it is acting upon, even if they share the same name.
- The ability of a message to be displayed in more than one form.
- It can be achieved through method overloading, method overriding, and operator overloading
Example: A person can have different characteristics at the same time. Like a man at the same time is a father, a husband, and an employee. So the same person possesses different behaviors in different situations. This is called polymorphism.
Method Overloading
- Method overloading allows a class to have multiple methods with the same name but with different signatures(number, type, or order).
- Compile-time Polymorphism
- Python does not natively support method overloading in the same way as some other languages (like Java or C++). Read further
add(int item1, int item2)
add(int item1, int item2,int item3)
add(string item1, string item2)
PythonMethod Overriding
- Method overriding allows a child class to provide a specific implementation of a method that is already defined in its parent class.
- Runtime polymorphism
class Animal:
def speak(self):
return "Some generic sound"
class Dog(Animal):
def speak(self):
return "Bark"
class Cat(Animal):
def speak(self):
return "Meow"
def make_animal_speak(animal):
print(animal.speak())
my_dog = Dog()
my_cat = Cat()
make_animal_speak(my_dog) # Output: Bark
make_animal_speak(my_cat) # Output: Meow
PythonIn this example, the sound method is defined in both the Animal class and its subclasses Dog and Cat. When the sound method is called on an object of the Dog or Cat class, the overridden method in the respective subclass is executed instead of the one in the Animal class.
Abstraction
- This principle involves hiding complex implementation details and showing only the essential features of an object.
- This allows the user to interact with an object without understanding its internal workings.
- In many OOP languages, abstract classes or interfaces implement abstraction.
- An abstract class cannot be instantiated and often includes one or more abstract methods that must be implemented by derived classes.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.1416 * self.radius * self.radius
my_circle = Circle(5)
print(my_circle.area()) # Output: 78.54
PythonAdvantages of OOP
- Modularity: Code is organized into distinct classes, making it easier to manage and maintain.
- Reusability: Inheritance and polymorphism promote code reuse.
- Flexibility: Encapsulation and abstraction allow for changes in code with minimal impact on other parts of the program.
- Scalability: OOP provides a structured way to develop complex software that can be easily scaled.
Association, Aggregation, and Composition
Association
- Association represents a general relationship between two or more objects where they interact with each other. It is the most basic form of a relationship.
- Bidirectional or Unidirectional: An association can be one-way (unidirectional) or two-way (bidirectional).
- Multiplicity: Specifies how many instances of one class are associated with one instance of another class (e.g., one-to-one, one-to-many).
- Example: A
Teacher
can be associated with multipleStudents
, and aStudent
can be associated with multipleTeachers
.
Example: A Driver
and a Car
have an association. A Driver
drives a Car
, and a Car
can be driven by a Driver
.
class Driver:
def __init__(self, name):
self.name = name
def drives(self, car):
print(f"{self.name} drives a {car.model}")
class Car:
def __init__(self, model):
self.model = model
# Example usage:
driver = Driver("Alice")
car = Car("Toyota Corolla")
driver.drives(car)
PythonOutput: Alice drives a Toyota Corolla
In this case, Driver
and Car
are associated, but they can exist independently.
Aggregation
- Aggregation is a specialized form of association, also known as a “has-a” relationship.
- It represents a whole-part relationship where the part can exist independently of the whole.
- Weak relationship: The lifecycle of the part is independent of the whole. If the whole object is destroyed, the part can still exist.
- Visual Representation: In UML diagrams, aggregation is represented by a hollow diamond.
- Example: A
Library
aggregatesBooks
. Even if theLibrary
is closed or demolished, theBooks
can still exist independently.
Example: A Library aggregates Books. Books belong to the library, but they can exist without the library.
class Book:
def __init__(self, title):
self.title = title
class Library:
def __init__(self, name):
self.name = name
self.books = []
def add_book(self, book):
self.books.append(book)
def show_books(self):
for book in self.books:
print(book.title)
# Example usage:
library = Library("City Library")
book1 = Book("1984 by George Orwell")
book2 = Book("To Kill a Mockingbird by Harper Lee")
library.add_book(book1)
library.add_book(book2)
library.show_books()
PythonOutput
1984 by George Orwell
To Kill a Mockingbird by Harper Lee
PythonIn this example, the library contains books, but books can exist even if they are destroyed.
Composition
- Composition is a more restrictive form of aggregation.
- It represents a whole-part relationship where the part cannot exist independently of the whole.
- Strong relationship: The lifecycle of the part is strictly tied to the lifecycle of the whole. If the whole object is destroyed, the part is also destroyed.
- Visual Representation: In UML diagrams, composition is represented by a filled diamond.
- Example: A
House
and itsRooms
. If theHouse
is destroyed, itsRooms
do not exist independently.
Example: A House is composed of Rooms. If the house is demolished, the rooms cease to exist.
class Room:
def __init__(self, name):
self.name = name
class House:
def __init__(self, address):
self.address = address
self.rooms = []
def add_room(self, room_name):
room = Room(room_name)
self.rooms.append(room)
def show_rooms(self):
for room in self.rooms:
print(f"Room: {room.name}")
# Example usage:
house = House("123 Maple Street")
house.add_room("Living Room")
house.add_room("Bedroom")
house.show_rooms()
PythonOutput:
Room: Living Room
Room: Bedroom
PythonIn this example, the House is composed of Rooms. If the House object is destroyed, the Rooms no longer exist.
Association | Aggregation | Composition |
General Relationship | Whole-Part Relationship | Strong Whole-Part Relationship |
No strong dependency | Objects are still relatively independent | Strong dependency between objects |
Objects can exist independently | The lifecycle of the part is not tied to the whole | The lifecycle of the part is strictly tied to the whole. |
Represents a “uses-a” or “knows-a” relationship | Represents a “has-a” relationship with some ownership | Represents a “contains-a” or “is-part-of” relationship |
Hierarchy Analogy:
- An association can be viewed as the most general concept, where objects have some kind of relationship.
- Aggregation is a more specific type of Association where there is a whole-part relationship, but the parts are independent.
- Composition is the most specific and strongest type of relationship, where the parts are entirely dependent on the whole.
Association
|
|-- Aggregation
| |
| |-- Composition
Python- Association is the broadest concept, encompassing any kind of relationship between objects.
- Aggregation is a specific type of Association where one object is a part of another but with less dependency.
- Composition is a specific type of Aggregation with the highest level of dependency between the whole and its parts.
Inheritance vs Association
Inheritance
- Inheritance is a mechanism where a new class (derived or child class) inherits the properties and behaviours (attributes and methods) of an existing class (base or parent class).
- It represents an “is-a” relationship.
- Purpose: Inheritance promotes code reusability by allowing a child class to reuse methods and attributes of the parent class. The child class can also override or extend the functionality of the parent class.
- Example:
- The dog is an Animal
- A student is a Person
Association
- The association represents a relationship where two or more classes are connected but remain independent of each other.
- It represents a “has-a” relationship and is used to show how objects interact with each other.
- Purpose: Association describes how objects work together, but each object has its own lifecycle and can exist independently of the other.
- Example
- The book has a Page
- The car has an engine
Inheritance
class Animal:
def speak(self):
..........
class Dog(<strong>Animal</strong>):
def speak(self):
..........
PythonAssociation
class Teacher:
def some_function(self, student):
..........
class Student:
def learn(self, teacher):
Teacher().some_function()
PythonImportant Concepts
A superclass reference variable can indeed refer to a subclass object
Java
Java/C++: A superclass reference variable can hold a reference to any subclass object, and the method that gets called depends on the actual object type at runtime.
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog();// Superclass reference to a subclass object
myAnimal.sound(); // Output will be "Dog barks"
}
}
PythonHere, myAnimal is a reference variable of the Animal superclass, but it refers to an instance of the Dog subclass. When you call the sound() method on myAnimal, the Dog class’s version of sound() is executed, demonstrating polymorphism.
Python
Since variables are dynamically typed, any variable can hold any object. The concept of a superclass reference holding a subclass object is not explicitly enforced, but the effect is similar due to Python’s dynamic typing and polymorphism.
class Animal:
def sound(self):
print("Animal makes a sound")
class Dog(Animal):
def sound(self):
print("Dog barks")
class Cat(Animal):
def sound(self):
print("Cat meows")
my_animal = Dog() # my_animal now references a Dog object
my_animal.sound() # Output: Dog barks
my_animal = Cat() # my_animal now references a Cat object
my_animal.sound() # Output: Cat meows
PythonIn Python, my_animal can refer to any object, including an object of a subclass, and Python will determine at runtime which sound() method to call:
Alternative to OOP
Here are a few common programming paradigms:
- Procedural Programming: This is one of the earliest and simplest styles. In procedural programming, code is written in a sequence of instructions or procedures. The focus is on functions or subroutines that operate on data rather than on data itself. It’s often structured with modular design but without objects or classes. Popular languages for procedural programming include C and BASIC.
- Functional Programming (FP): Functional programming is based on mathematical functions. It emphasizes immutability, where data is not modified after creation, and pure functions, where the output is determined solely by the input. FP avoids shared state and side effects, making it suitable for parallel processing. Common functional programming languages include Haskell, Lisp, and newer implementations in JavaScript and Python.
- Declarative Programming: In declarative programming, you describe what you want to achieve rather than how to achieve it. SQL, for example, is declarative; you specify what data you need but not how to retrieve it. Similarly, HTML is declarative because it defines how a webpage should look without detailing the rendering process.
- Logic Programming: This paradigm is based on formal logic. Instead of telling the computer what to do, you specify rules and relationships, and the computer deduces the solution. Prolog is a well-known logic programming language, commonly used in AI and computational linguistics.
- Event-Driven Programming: This is often used in GUIs, where program execution is driven by user actions or events (like clicks, keystrokes, etc.). It’s common in languages like JavaScript, where interactions trigger specific code routines.
- Aspect-Oriented Programming (AOP): AOP complements OOP by allowing the separation of cross-cutting concerns, such as logging, security, and error handling. It enables you to add behaviour to code without modifying the actual code, typically through the use of aspects, join points, and advice.
- Reactive Programming: Reactive programming is a paradigm focused on asynchronous data streams and the propagation of change. It’s highly suitable for applications that need to handle real-time data or user interaction, like live feeds or dynamic UI updates. This style is particularly prominent in modern JavaScript frameworks (such as RxJS in Angular) and reactive systems in languages like Java (Project Reactor) and Kotlin.
Conclusion
Object-Oriented Programming (OOP) is a powerful and flexible paradigm that allows developers to create modular, reusable, and scalable software. By organizing code around objects—instances of classes—OOP makes it easier to model real-world problems and manage complex systems. The four fundamental principles of OOP—encapsulation, inheritance, polymorphism, and abstraction—provide a robust framework for designing and implementing software.
Polymorphism, in particular, plays a crucial role in OOP by allowing objects to be treated as instances of their parent class, enabling method overloading and overriding. These techniques enhance the flexibility and maintainability of code, allowing for the seamless extension and modification of software without disrupting existing functionality.
By mastering OOP concepts, developers can write more efficient, organized, and maintainable code, making it easier to collaborate on large projects and adapt to future requirements. Whether you are building small applications or large-scale systems, OOP principles serve as a foundational approach that can be applied across various programming languages and development environments.