🖥️ VALLEYS & BYTES/ OOP-sitemap

Object‑Oriented Programming
intensive technical directory

domain: https://code-sense.pansensoyglenn.workers.dev — full OOP architecture guide with Python deep dives

Advertisement

📊 OOP in Production: Key Metrics

78%

of Python codebases use OOP extensively

43%

reduction in bugs with SOLID compliance

3.2x

faster refactoring with design patterns

01. Data Invariants & Encapsulation — controlling state

In professional engineering, encapsulation is the primary mechanism for maintaining data invariants. By restricting direct access to an object's state, we ensure that the object remains in a valid configuration throughout its lifecycle. Python implements this via naming conventions: _protected for internal scope (developer convention) and __private for name mangling (prevents accidental override in subclasses).

Leveraging @property decorators allows for "controlled access," where logic can be injected during attribute retrieval or assignment. This is where invariants are enforced (e.g., non‑negative balance, valid email format). Below a robust example with getter/setter and a hidden attribute.

Production-Grade Encapsulation with Validation

class UserAccount:
    def __init__(self, email: str, initial_balance: float = 0):
        self._email = None
        self.__balance = 0
        self.email = email  
        self.balance = initial_balance

    @property
    def email(self) -> str:
        return self._email

    @email.setter
    def email(self, value: str):
        if '@' not in value or '.' not in value.split('@')[-1]:
            raise ValueError("Invalid email format")
        self._email = value

    @property
    def balance(self) -> float:
        return self.__balance

    @balance.setter
    def balance(self, value: float):
        if value < 0:
            raise ValueError("Balance cannot be negative")
        if value > 1000000:
            raise ValueError("Balance exceeds maximum limit")
        self.__balance = value

    def deposit(self, amount: float):
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        self.balance += amount

🔬 Performance Consideration: Using @property adds ~0.5μs overhead per access. For millions of operations, consider direct attribute access or __slots__.

Invariant enforcement: the setter guarantees that balance never goes below zero and email is always valid. The mangled name __balance discourages direct manipulation from outside.

🔍 Deep insight: Name mangling exists primarily to avoid accidental attribute collisions in subclasses, not for security. Real encapsulation relies on conventions and properties — trust the developer, protect the invariant.

Advertisement

02. SOLID Engineering Standards (five pillars)

Certified software systems must adhere to the SOLID framework to ensure maintainability, testability, and resilience to shifting requirements. Each principle addresses a specific fragility.

🔹 SRP (Single Responsibility)

A module should encapsulate one business concern. Avoid "God Objects" that manage multiple domains. Example: separate ReportGenerator from ReportPrinter.

🔸 OCP (Open/Closed)

Entities should be open for extension, closed for modification. Achieved via polymorphism / strategy pattern. Add new features by writing new code, not altering tested existing code.

🔹 LSP (Liskov Substitution)

Derived classes must be substitutable for their base classes without altering correctness. If a function works with Bird, it must work with Penguin (even if penguins don't fly — maybe redesign).

🔸 ISP (Interface Segregation)

Clients should not be forced to depend on methods they do not use. Split large interfaces into smaller, focused ones (e.g., Printer and Scanner instead of AllInOne).

🔹 DIP (Dependency Inversion)

High-level modules should depend on abstract interfaces, not concrete implementations. This facilitates unit testing (mocking) and decoupling. DatabaseService depends on StorageInterface, not MySQLConnection.

Example: DIP with dependency injection

class Storage(ABC):
    @abstractmethod def save(data): ...

class S3Storage(Storage): ...
class LocalStorage(Storage): ...

class DataService:
    def __init__(self, storage: Storage):
        self._storage = storage

Common SOLID Violations to Avoid

PrincipleViolationFix
SRPClass handling both auth and loggingSplit into AuthService + Logger
OCPif-else chains for new featuresUse strategy pattern
LSPSquare inheriting RectangleUse common Shape interface
ISPFat interfaces with unused methodsSplit into smaller interfaces
DIPHigh-level module imports low-levelInject abstractions

03. Compositional Logic over Inheritance

While inheritance establishes an "Is-A" relationship, it often leads to rigid hierarchies and fragile base class problems. Senior architects favor Composition (the "Has-A" relationship) to inject behavior dynamically at runtime, improving flexibility and testability.

Mixins are a special form of composition via multiple inheritance that add reusable chunks of behavior. Dependency injection is a composition technique where required collaborators are passed in (often through constructor).

Real-world: Composable Notification System

class Notifier:
    def send(self, message): pass

class EmailNotifier(Notifier):
    def send(self, message): print(f"Email: {message}")

class SMSNotifier(Notifier):
    def send(self, message): print(f"SMS: {message}")

class LoggingMixin:
    def log(self, msg): print(f"LOG: {msg}")

class AlertService(LoggingMixin):
    def __init__(self, notifiers: List[Notifier]):
        self._notifiers = notifiers
    
    def alert(self, message):
        self.log("Sending alert")
        for notifier in self._notifiers:
            notifier.send(message)

# Compose at runtime
service = AlertService([EmailNotifier(), SMSNotifier()])

Benefits: loose coupling, easier mocking (pass a fake pool), behavior reuse without deep hierarchy.

🎯 Rule of thumb: "Prefer composition over inheritance" unless you need polymorphic behavior AND code reuse through an "is-a" relationship.

Advertisement

04. Creational Pattern Registry — Factory, AbstractFactory, Builder

Design patterns provide a standardized vocabulary for solving recurring structural problems. Creational patterns abstract the instantiation process, making the system independent of how objects are created.

Builder Pattern: Complex Object Construction

class SQLQueryBuilder:
    def __init__(self):
        self._query = ""
    
    def select(self, *fields):
        self._query += f"SELECT {', '.join(fields)} "
        return self
    
    def from_table(self, table):
        self._query += f"FROM {table} "
        return self
    
    def where(self, condition):
        self._query += f"WHERE {condition} "
        return self
    
    def build(self) -> str:
        return self._query.strip()

# Usage
query = (SQLQueryBuilder()
         .select("name", "email")
         .from_table("users")
         .where("age > 18")
         .build())

Registry + Factory example:

class Document: pass
class PDFDoc(Document): ...
class HTMLDoc(Document): ...

class DocumentFactory:
    _registry = {}
    @classmethod def register(cls, ext, builder):
        cls._registry[ext] = builder
    @classmethod def create(cls, ext, title):
        return cls._registry[ext](title)

DocumentFactory.register('pdf', PDFDoc)
doc = DocumentFactory.create('pdf', 'report')

05. Memory Optimization Protocols — __slots__, weakref, struct

In high-throughput systems, the default Python __dict__ overhead is non-trivial. Using __slots__ allows for fixed attribute allocation, significantly reducing the memory footprint per instance in data-intensive applications (e.g., millions of objects). Additionally, weakref enables references that don't prevent garbage collection — crucial for caches.

Memory Footprint Comparison

TechniqueMemory per 1M objectsAccess Speed
Regular class (__dict__)~80 MBBaseline
__slots__~40 MB (50% reduction)15% faster
NamedTuple~32 MB20% faster
dataclass + slots=True~42 MB15% faster
class Point:
    __slots__ = ('x', 'y', 'z')
    def __init__(self, x, y, z):
        self.x, self.y, self.z = x, y, z

Weak references avoid memory leaks in observer patterns or caches:

import weakref
class Observer: ...
class Subject:
    def __init__(self):
        self._observers = weakref.WeakSet()

Pro tip: Use __slots__ when creating >100,000 instances. For smaller counts, the maintenance overhead isn't worth it.

Advertisement

06. Polymorphism & Dynamic Dispatch — duck typing, ABC

Polymorphism enables objects of different types to respond to the same interface. Python excels with duck typing: “if it walks like a duck and quacks like a duck, it's a duck”. Abstract Base Classes (ABC) formalize interfaces and can enforce method implementation.

Practical: Plugin System with Polymorphism

from abc import ABC, abstractmethod

class DataProcessor(ABC):
    @abstractmethod
    def process(self, data):
        pass
    
    @abstractmethod
    def validate(self, data):
        pass

class CSVProcessor(DataProcessor):
    def process(self, data):
        return [row.split(',') for row in data.split('\n')]
    
    def validate(self, data):
        return all(',' in row for row in data.split('\n'))

class JSONProcessor(DataProcessor):
    def process(self, data):
        import json
        return json.loads(data)
    
    def validate(self, data):
        try:
            import json; json.loads(data)
            return True
        except:
            return False

# Polymorphic dispatch
def handle_data(processor: DataProcessor, raw_data):
    if processor.validate(raw_data):
        return processor.process(raw_data)
    raise ValueError("Invalid data format")
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod def area(self): pass

class Circle(Shape):
    def __init__(self, radius): self.r = radius
    def area(self): return 3.14 * self.r ** 2

class Duck:
    def quack(self): print("quack")
class Person:
    def quack(self): print("i try to quack")

def make_it_quack(thing): thing.quack()

07. Behavioral Patterns — Observer, Strategy, Template

Behavioral patterns focus on communication between objects, encapsulating varying behavior.

Strategy Pattern: Payment Processing

from abc import ABC, abstractmethod

class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount): pass

class CreditCardPayment(PaymentStrategy):
    def __init__(self, card_number):
        self.card_number = card_number
    
    def pay(self, amount):
        print(f"Paid ${amount} with credit card {self.card_number[-4:]}")

class PayPalPayment(PaymentStrategy):
    def __init__(self, email):
        self.email = email
    
    def pay(self, amount):
        print(f"Paid ${amount} via PayPal account {self.email}")

class ShoppingCart:
    def __init__(self, payment_strategy: PaymentStrategy):
        self.items = []
        self.payment_strategy = payment_strategy
    
    def checkout(self):
        total = sum(item.price for item in self.items)
        self.payment_strategy.pay(total)

# Usage
cart = ShoppingCart(CreditCardPayment("1234-5678-9012-3456"))
cart.checkout()

Observer minimal example:

class Subject:
    def __init__(self): self.obs = []
    def attach(self, o): self.obs.append(o)
    def notify(self):
        for o in self.obs: o.update(self)

class ConcreteObserver:
    def update(self, subject): print("updated")

Strategy and Template are widely used in frameworks — e.g., Django's class‑based views use template method (get, post hooks).

Advertisement

08. Design by Contract & Invariants — pre/postconditions

Design by Contract (DbC) specifies formal, precise and verifiable interface specifications. In Python, we enforce contracts using assertions, exceptions, or libraries (e.g., icontract). Preconditions (what must be true before a method runs), postconditions (what must be true after), and class invariants (always true) increase reliability.

Complete Contract Example with Invariants

class BankAccount:
    def __init__(self, owner, initial_balance):
        self.owner = owner
        self._balance = 0
        assert initial_balance >= 0, "Initial balance must be non-negative"
        self._balance = initial_balance
        self._invariant()
    
    def _invariant(self):
        assert self._balance >= 0, "Invariant violated: balance negative"
    
    def withdraw(self, amount):
        if amount <= 0:
            raise ValueError("amount must be positive")
        if amount > self._balance:
            raise ValueError("Insufficient funds")
        
        old_balance = self._balance
        self._balance -= amount
        
        assert self._balance == old_balance - amount
        self._invariant()  
        return self._balance

🧱 Architectural note: Explicit contracts serve as built‑in documentation and early error detection. They are especially valuable in long‑lived, critical systems.

📌 OOP Best Practices Cheat Sheet

  • ✅ Favor composition over inheritance
  • ✅ Keep classes focused (SRP)
  • ✅ Use ABCs for interfaces
  • ✅ Document pre/post conditions
  • ✅ Consider __slots__ for many objects
  • ✅ Use @property for controlled access
  • ✅ Inject dependencies
  • ✅ Design for testability

Advertisement