Object-Oriented Programming (OOP) Explained: A Complete Guide
Comprehensive Insights into Object-Oriented Programming System (OOPs)
Object-Oriented Programming System (OOPs) is a powerful programming paradigm that uses objects and classes to design and develop software. This approach allows for better organization, modularity, and reuse of code. In this blog, we will cover the foundational and advanced concepts of OOPs, including classes, objects, constructors, destructors, encapsulation, inheritance, polymorphism, abstraction, and advanced OOPs principles.
Basic Concepts of OOPs
1. Classes and Objects
Classes
A class is a blueprint or template for creating objects. It defines the attributes (data) and methods (functions) that the objects created from the class will have.
Example:
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def display_info(self):
print(f"Car: {self.year} {self.make} {self.model}")
Objects
Objects are instances of classes. When a class is defined, no memory is allocated until an object of that class is created.
Example:
my_car = Car("Toyota", "Corolla", 2020)
my_car.display_info() # Output: Car: 2020 Toyota Corolla
2. Constructors and Destructors
Constructors
A constructor is a special method called when an object is instantiated. It is used to initialize the object's state.
Example:
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
Destructors
A destructor is a special method called when an object is destroyed. It is used to clean up resources. In Python, the __del__
method acts as a destructor.
Example:
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def __del__(self):
print(f"Car {self.make} {self.model} is being destroyed")
3. Encapsulation
Encapsulation is the mechanism of wrapping the data (variables) and code (methods) together as a single unit. It restricts direct access to some of an object's components, which is a means of preventing accidental interference and misuse of the data.
Example:
class Account:
def __init__(self, owner, balance):
self.owner = owner
self.__balance = balance # Private attribute
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
acct = Account("John Doe", 1000)
acct.deposit(500)
print(acct.get_balance()) # Output: 1500
4. Inheritance
Inheritance is a mechanism where a new class inherits the attributes and methods of an existing class. The class that is inherited from is called the parent or base class, and the class that inherits is called the child or derived class.
Example:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Output: Woof!
print(cat.speak()) # Output: Meow!
5. Polymorphism
Polymorphism allows objects of different classes to be treated as objects of a common superclass. It is the ability to redefine methods for derived classes.
Example:
class Bird:
def fly(self):
print("Flying")
class Sparrow(Bird):
def fly(self):
print("Sparrow is flying")
class Ostrich(Bird):
def fly(self):
print("Ostriches can't fly")
def make_fly(bird):
bird.fly()
sparrow = Sparrow()
ostrich = Ostrich()
make_fly(sparrow) # Output: Sparrow is flying
make_fly(ostrich) # Output: Ostriches can't fly
6. Abstraction
Abstraction is the concept of hiding the complex implementation details and showing only the essential features of the object. It helps in reducing programming complexity and effort.
Example:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
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)
rect = Rectangle(4, 7)
print(f"Area: {rect.area()}") # Output: Area: 28
print(f"Perimeter: {rect.perimeter()}") # Output: Perimeter: 22
Advanced OOPs Concepts
1. Multiple Inheritance
Multiple inheritance allows a class to inherit from more than one base class. This can be useful but can also introduce complexity and ambiguity, particularly with the diamond problem.
Example:
class Animal:
def eat(self):
print("Eating")
class Bird(Animal):
def fly(self):
print("Flying")
class Fish(Animal):
def swim(self):
print("Swimming")
class FlyingFish(Bird, Fish):
def fly_swim(self):
self.fly()
self.swim()
ff = FlyingFish()
ff.eat() # Output: Eating
ff.fly_swim() # Output: Flying \n Swimming
2. Mixins
Mixins are a form of multiple inheritance where the classes being inherited from are not meant to stand alone but provide additional functionality to the derived class.
Example:
class Loggable:
def log(self, msg):
print(f"Log: {msg}")
class Saveable:
def save(self):
print("Data saved")
class Account(Loggable, Saveable):
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
acct = Account("John Doe", 1000)
acct.log("Account created") # Output: Log: Account created
acct.save() # Output: Data saved
3. Method Overriding and Super Calls
Method overriding allows a child class to provide a specific implementation of a method that is already defined in its parent class. The super()
function is used to call the method of the parent class.
Example:
class Parent:
def greet(self):
print("Hello from Parent")
class Child(Parent):
def greet(self):
super().greet()
print("Hello from Child")
child = Child()
child.greet()
# Output:
# Hello from Parent
# Hello from Child
4. SOLID Principles
The SOLID principles are a set of design principles intended to make software designs more understandable, flexible, and maintainable.
Single Responsibility Principle (SRP)
A class should have only one reason to change, meaning it should have only one job or responsibility.
Example:
class Order:
def __init__(self, items):
self.items = items
def calculate_total(self):
return sum(item.price for item in self.items)
class OrderPrinter:
def print_order(self, order):
for item in order.items:
print(f"{item.name}: {item.price}")
# Order class is responsible for order management
# OrderPrinter class is responsible for printing the order
Open/Closed Principle (OCP)
Software entities should be open for extension but closed for modification.
Example:
class Discount:
def apply_discount(self, total):
pass
class TenPercentDiscount(Discount):
def apply_discount(self, total):
return total * 0.9
class Order:
def __init__(self, items, discount):
self.items = items
self.discount = discount
def calculate_total(self):
total = sum(item.price for item in self.items)
return self.discount.apply_discount(total)
Liskov Substitution Principle (LSP)
Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
Example:
class Bird:
def fly(self):
pass
class Sparrow(Bird):
def fly(self):
print("Sparrow is flying")
class Ostrich(Bird):
def fly(self):
raise Exception("Ostriches can't fly")
def make_fly(bird: Bird):
bird.fly()
# Here, make_fly should work with any Bird subclass without error.
Interface Segregation Principle (ISP)
Clients should not be forced to depend on interfaces they do not use.
Example:
class Printer:
def print(self, document):
pass
class Scanner:
def scan(self, document):
pass
class MultiFunctionDevice(Printer, Scanner):
def print(self, document):
print(f"Printing: {document}")
def scan(self, document):
print(f"Scanning: {document}")
Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Example:
class Database:
def get_data(self):
pass
class MySQLDatabase(Database):
def get_data(self):
return "MySQL Data"
class BusinessLogic:
def __init__(self, database: Database):
self.database = database
def process_data(self):
data = self.database.get_data()
print(f"Processing {data}")
db = MySQLDatabase()
logic = BusinessLogic(db)
logic.process_data()
# Output: Processing MySQL Data
5. Design Patterns
Design patterns are typical solutions to common problems in software design. They are like pre-made blueprints that you can customize to solve a recurring design problem in your code. There are three main types of design patterns: creational, structural, and behavioral.
Singleton Pattern
Ensures a class has only one instance and provides a global point of access to it.
Example:
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
singleton1 = Singleton()
singleton2 = Singleton()
print(singleton1 is singleton2) # Output: True
Factory Pattern
Defines an interface for creating an object but lets subclasses alter the type of objects that will be created.
Example:
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class AnimalFactory:
@staticmethod
def create_animal(animal_type):
if animal_type == "dog":
return Dog()
elif animal_type == "cat":
return Cat()
dog = AnimalFactory.create_animal("dog")
print(dog.speak()) # Output: Woof!
Observer Pattern
Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Example:
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def notify(self, message):
for observer in self._observers:
observer.update(message)
class Observer:
def update(self, message):
pass
class ConcreteObserver(Observer):
def update(self, message):
print(f"Received message: {message}")
subject = Subject()
observer1 = ConcreteObserver()
observer2 = ConcreteObserver()
subject.attach(observer1)
subject.attach(observer2)
subject.notify("Hello Observers!")
# Output:
# Received message: Hello Observers!
# Received message: Hello Observers!
Conclusion
I hope it is not boring ๐. Do comment and share!