Ojasa Mirai

Ojasa Mirai

Python

Loading...

Learning Level

🟢 Beginner🔵 Advanced
Classes & ObjectsMethods & SelfInstance VariablesClass VariablesConstructors & InitializationInheritance BasicsPolymorphismEncapsulationMagic Methods & Dunder
Python/Oop/Classes And Objects

🏗️ Classes & Objects — Advanced Patterns and Architectures

Advanced class design involves understanding metaclasses, design patterns, descriptor protocol, and sophisticated object creation strategies. Professional Python code uses patterns that scale to large systems while maintaining flexibility and clarity.


🎯 Factory Pattern

The factory pattern creates objects without specifying exact classes. This decouples object creation from usage, making systems more flexible and maintainable.

from abc import ABC, abstractmethod

class Database(ABC):
    @abstractmethod
    def connect(self):
        pass

class PostgresDB(Database):
    def connect(self):
        return "Connected to PostgreSQL"

class MongoDBConnection(Database):
    def connect(self):
        return "Connected to MongoDB"

class DatabaseFactory:
    _databases = {
        "postgres": PostgresDB,
        "mongodb": MongoDBConnection
    }

    @staticmethod
    def create_database(db_type):
        if db_type not in DatabaseFactory._databases:
            raise ValueError(f"Unknown database: {db_type}")
        return DatabaseFactory._databases[db_type]()

    @staticmethod
    def register(db_type, db_class):
        DatabaseFactory._databases[db_type] = db_class

# Use factory
postgres = DatabaseFactory.create_database("postgres")
print(postgres.connect())

mongodb = DatabaseFactory.create_database("mongodb")
print(mongodb.connect())

# Register new database type
class SQLiteDB(Database):
    def connect(self):
        return "Connected to SQLite"

DatabaseFactory.register("sqlite", SQLiteDB)
sqlite = DatabaseFactory.create_database("sqlite")

🔨 Singleton Pattern

A singleton ensures only one instance of a class exists. Useful for shared resources like database connections or configuration managers.

class Singleton:
    _instance = None

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

class ConfigManager(Singleton):
    def __init__(self):
        if not hasattr(self, 'initialized'):
            self.settings = {}
            self.initialized = True

    def set(self, key, value):
        self.settings[key] = value

    def get(self, key):
        return self.settings.get(key)

# Same instance everywhere
config1 = ConfigManager()
config1.set("debug", True)

config2 = ConfigManager()
print(config2.get("debug"))  # Output: True

print(config1 is config2)    # Output: True

📋 Abstract Base Classes

Abstract base classes define interfaces that subclasses must implement. They enforce contract compliance.

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def process(self, amount):
        """Process payment"""
        pass

    @abstractmethod
    def refund(self, amount):
        """Refund payment"""
        pass

    def validate(self, amount):
        """Concrete method available to all"""
        return amount > 0

class StripeProcessor(PaymentProcessor):
    def process(self, amount):
        if not self.validate(amount):
            raise ValueError("Invalid amount")
        return f"Processed ${amount} via Stripe"

    def refund(self, amount):
        return f"Refunded ${amount} from Stripe"

class PayPalProcessor(PaymentProcessor):
    def process(self, amount):
        if not self.validate(amount):
            raise ValueError("Invalid amount")
        return f"Processed ${amount} via PayPal"

    def refund(self, amount):
        return f"Refunded ${amount} from PayPal"

# Cannot instantiate abstract class
# processor = PaymentProcessor()  # Error!

stripe = StripeProcessor()
print(stripe.process(99.99))

🔍 Descriptor Protocol

Descriptors are objects that define how attributes are accessed. They provide hooks for getting, setting, and deleting attributes.

class Descriptor:
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj.__dict__.get(self.name, None)

    def __set__(self, obj, value):
        raise AttributeError("Cannot set this attribute")

    def __set_name__(self, owner, name):
        self.name = name

class Email(Descriptor):
    def __set__(self, obj, value):
        if '@' not in value:
            raise ValueError("Invalid email")
        obj.__dict__[self.name] = value

class User:
    email = Email()

    def __init__(self, name, email):
        self.name = name
        self.email = email

user = User("Alice", "alice@example.com")
print(user.email)  # Output: alice@example.com

try:
    user.email = "invalid"
except ValueError as e:
    print(f"Error: {e}")

🌳 Multiple Inheritance and MRO

Method Resolution Order (MRO) determines how Python looks up methods in multiple inheritance hierarchies using C3 linearization.

class A:
    def method(self):
        return "A"

class B(A):
    def method(self):
        return "B -> " + super().method()

class C(A):
    def method(self):
        return "C -> " + super().method()

class D(B, C):
    pass

d = D()
print(d.method())  # Output: B -> C -> A
print(D.mro())     # Shows method resolution order

# MRO: [D, B, C, A, object]

📚 Real-World Examples

Plugin System

from abc import ABC, abstractmethod

class Plugin(ABC):
    @abstractmethod
    def execute(self):
        pass

class PluginManager:
    def __init__(self):
        self.plugins = {}

    def register(self, name, plugin_class):
        self.plugins[name] = plugin_class

    def load(self, name):
        if name not in self.plugins:
            raise ValueError(f"Plugin {name} not found")
        return self.plugins[name]()

class EmailPlugin(Plugin):
    def execute(self):
        return "Sending email..."

class SlackPlugin(Plugin):
    def execute(self):
        return "Posting to Slack..."

manager = PluginManager()
manager.register("email", EmailPlugin)
manager.register("slack", SlackPlugin)

email = manager.load("email")
print(email.execute())

slack = manager.load("slack")
print(slack.execute())

Object Registry Pattern

class Registered:
    _registry = {}

    def __init_subclass__(cls):
        cls._registry[cls.__name__] = cls

    @classmethod
    def get_subclass(cls, name):
        return cls._registry.get(name)

    @classmethod
    def list_subclasses(cls):
        return list(cls._registry.keys())

class Animal(Registered):
    pass

class Dog(Animal):
    pass

class Cat(Animal):
    pass

class Bird(Animal):
    pass

print(Animal.list_subclasses())
# Output: ['Dog', 'Cat', 'Bird']

dog_class = Animal.get_subclass("Dog")
print(dog_class)

✅ Key Takeaways

ConceptRemember
Factory PatternCreate objects without specifying exact classes
SingletonEnsure only one instance exists
Abstract ClassesDefine interfaces subclasses must implement
DescriptorsControl attribute access with __get__/__set__
MROMethod Resolution Order in multiple inheritance
Plugin SystemRegister and load classes dynamically
Design PatternsReusable solutions to common problems
DecouplingSeparate object creation from usage

🔗 What's Next?

Explore more advanced patterns and techniques in the other advanced topics.

Advanced: Methods & Self →


Ready to practice? Try challenges or explore more concepts


Resources

Python Docs

Ojasa Mirai

Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.

Learn Deep • Build Real • Verify Skills • Launch Forward

Courses

PythonFastapiReactJSCloud

© 2026 Ojasa Mirai. All rights reserved.

TwitterGitHubLinkedIn