Complex OOP in Python — From Basics to Advanced Patterns

Invalid Date (NaNy ago)

🧠 Complex Object-Oriented Programming (OOP) in Python

Author: Shaukat

Object-Oriented Programming (OOP) is not just about syntax — it's a philosophy, a design methodology that helps you model real-world entities in software. Let’s move beyond the basics and deeply explore how OOP scales from simple use to complex patterns.


🌱 Basics: Class & Object (with Theory)

Theory: A class is a blueprint. An object is an instance of that blueprint. You use classes when you want to model entities with state (attributes) and behavior (methods).

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    def greet(self):
        print(f"Hello, I’m {self.name}, {self.age} years old.")
 
shaukat = Person("Shaukat", 25)
shaukat.greet()

🧱 Encapsulation, Abstraction, Inheritance, Polymorphism

📦 Encapsulation

Theory: Encapsulation hides internal state and forces interaction through an object's methods. This ensures data integrity and secure APIs.

class BankAccount:
    def __init__(self, owner):
        self.__balance = 0
        self.owner = owner
 
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
 
    def get_balance(self):
        return self.__balance
 
shaukat_account = BankAccount("Shaukat")
shaukat_account.deposit(1000)
print(shaukat_account.get_balance())

🧊 Abstraction

Theory: Abstraction lets you expose only relevant functionality, hiding internal workings. Abstract Base Classes (ABCs) enforce contracts.

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.14 * self.radius ** 2
 
circle = Circle(5)
print(circle.area())

🧬 Inheritance

Theory: Inheritance enables reuse and hierarchical relationships. It supports DRY (Don't Repeat Yourself) principles.

class Employee:
    def __init__(self, name):
        self.name = name
 
    def work(self):
        return f"{self.name} is working."
 
class Developer(Employee):
    def work(self):
        return f"{self.name} is writing code."
 
shaukat_dev = Developer("Shaukat")
print(shaukat_dev.work())

🎭 Polymorphism

Theory: Polymorphism allows objects of different types to be treated uniformly through a shared interface.

def describe_worker(worker):
    print(worker.work())
 
dev = Developer("Shaukat")
emp = Employee("Ali")
describe_worker(dev)
describe_worker(emp)

🔮 Advanced Concepts

1. Classmethod vs Staticmethod vs Instance Method

class App:
    app_count = 0
 
    def __init__(self):
        App.app_count += 1
 
    @classmethod
    def get_count(cls):
        return cls.app_count
 
    @staticmethod
    def info():
        return "This is a general-purpose app."
 
print(App.get_count())
print(App.info())

Theory:


2. Composition over Inheritance

Theory: Composition provides greater flexibility than inheritance. You build objects using other objects instead of creating deep hierarchies.

class Engine:
    def start(self):
        print("Engine starting...")
 
class Car:
    def __init__(self):
        self.engine = Engine()
 
    def drive(self):
        self.engine.start()
        print("Car is moving")
 
shaukat_car = Car()
shaukat_car.drive()

🧩 Design Patterns (Applied OOP)

📐 1. Singleton Pattern

Significance: Guarantees a single instance globally (used in logging, DB connections).

class Singleton:
    _instance = None
 
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
 
obj1 = Singleton()
obj2 = Singleton()
print(obj1 is obj2)  # True

📦 2. Factory Pattern

Significance: Abstracts object creation, useful when classes are chosen based on runtime logic.

class Notification:
    def notify(self):
        pass
 
class Email(Notification):
    def notify(self):
        print("Email sent")
 
class SMS(Notification):
    def notify(self):
        print("SMS sent")
 
def notification_factory(type):
    if type == "email":
        return Email()
    return SMS()
 
note = notification_factory("email")
note.notify()

🚀 Extremely Advanced Patterns

🎯 Strategy Pattern

Significance: Replaces conditional logic with interchangeable behaviors at runtime.

class SortStrategy:
    def sort(self, data): pass
 
class BubbleSort(SortStrategy):
    def sort(self, data):
        return sorted(data)
 
class QuickSort(SortStrategy):
    def sort(self, data):
        return sorted(data, reverse=True)
 
class Sorter:
    def __init__(self, strategy):
        self.strategy = strategy
 
    def set_strategy(self, strategy):
        self.strategy = strategy
 
    def sort_data(self, data):
        return self.strategy.sort(data)
 
shaukat_sorter = Sorter(BubbleSort())
print(shaukat_sorter.sort_data([3, 2, 1]))

🕵️‍♂️ Observer Pattern

Significance: Enables reactive programming. Great for GUIs, game engines, etc.

class Subject:
    def __init__(self):
        self._observers = []
 
    def register(self, observer):
        self._observers.append(observer)
 
    def notify(self):
        for obs in self._observers:
            obs.update()
 
class Listener:
    def update(self):
        print("Notified!")
 
sub = Subject()
obs1 = Listener()
sub.register(obs1)
sub.notify()

🧠 Adding SOLID Principles and UML Diagrams

To truly master OOP, you must also:

These practices help in designing flexible, extensible, and maintainable software systems — the kind that scale and survive over time.


🏗 Additional Patterns & Meta Programming

🧰 Decorator Pattern

Significance: Add new behavior to objects at runtime without modifying their code.

def log_function_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper
 
class Calculator:
    @log_function_call
    def add(self, x, y):
        return x + y
 
shaukat_calc = Calculator()
print(shaukat_calc.add(3, 4))

🧬 Metaclasses

Theory: Metaclasses let you control class creation — useful in frameworks (like Django).

class Meta(type):
    def __new__(cls, name, bases, attrs):
        attrs['id'] = 1
        return super().__new__(cls, name, bases, attrs)
 
class MyClass(metaclass=Meta):
    pass
 
print(MyClass.id)  # Output: 1

📘 Conclusion

OOP is not just syntax — it’s a design mindset. Understanding why and how each pattern works will help you:

Whether you’re implementing a Singleton for a config, using Strategy to choose algorithms dynamically, or building reactive systems with Observer — mastering these patterns is crucial for professional development.

Use OOP intentionally. That’s the path