OOP & Patterns
Object-oriented programming and design patterns
Object-Oriented Programming & Design Patterns in Python
Master object-oriented programming (OOP) and design patterns with free flashcards and spaced repetition practice. This comprehensive lesson covers class design, inheritance, polymorphism, SOLID principles, and essential design patterns like Singleton, Factory, and Observerโcritical skills for writing maintainable, scalable Python applications.
Welcome to OOP & Design Patterns! ๐ป
Welcome to one of the most transformative topics in Python programming! Object-oriented programming isn't just a feature of Pythonโit's a fundamental way of thinking about code organization and problem-solving. Design patterns take this further by providing battle-tested solutions to common software design challenges.
In this lesson, you'll learn how to:
- Design robust classes with proper encapsulation and abstraction
- Leverage inheritance and polymorphism to write reusable code
- Apply SOLID principles for maintainable software architecture
- Implement proven design patterns that professional developers use daily
- Refactor procedural code into elegant object-oriented solutions
Whether you're building web applications, data pipelines, or automation tools, these concepts will elevate your Python skills to a professional level.
Core Concepts of Object-Oriented Programming ๐ฏ
Classes and Objects: The Foundation
Classes are blueprints that define the structure and behavior of objects. An object (or instance) is a specific realization of a class with actual data.
Key terminology:
- Attributes: Variables that store object state (data)
- Methods: Functions that define object behavior (actions)
- Constructor: Special
__init__method that initializes new objects - self: Reference to the current instance
class Dog:
# Class attribute (shared by all instances)
species = "Canis familiaris"
def __init__(self, name, age):
# Instance attributes (unique to each instance)
self.name = name
self.age = age
def bark(self):
return f"{self.name} says Woof!"
def celebrate_birthday(self):
self.age += 1
return f"{self.name} is now {self.age} years old!"
# Creating objects
buddy = Dog("Buddy", 3)
max_dog = Dog("Max", 5)
print(buddy.bark()) # Output: Buddy says Woof!
print(buddy.species) # Output: Canis familiaris
๐ก Memory tip: Think of a class as a cookie cutter, and objects as the individual cookies it producesโsame shape, different decorations!
The Four Pillars of OOP ๐๏ธ
| Pillar | Description | Python Implementation |
|---|---|---|
| Encapsulation | Bundling data with methods that operate on that data; hiding internal details | Private attributes with _ or __ prefix |
| Abstraction | Exposing only essential features while hiding complexity | Abstract base classes (ABC module) |
| Inheritance | Creating new classes based on existing ones | class Child(Parent): |
| Polymorphism | Objects of different types responding to the same interface | Method overriding, duck typing |
Encapsulation: Protecting Your Data ๐
Encapsulation controls access to object internals, preventing accidental modification and maintaining data integrity.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # Private attribute (name mangling)
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return f"Deposited ${amount}. New balance: ${self.__balance}"
return "Invalid amount"
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
return f"Withdrew ${amount}. Remaining: ${self.__balance}"
return "Insufficient funds or invalid amount"
@property
def balance(self):
"""Read-only access to balance"""
return self.__balance
account = BankAccount("Alice", 1000)
print(account.deposit(500)) # Works through public method
print(account.balance) # Read-only property: 1500
# account.__balance = 999999 # Won't affect actual balance (name mangling)
Privacy conventions in Python:
public_attr: Public, accessible anywhere_protected_attr: Protected, internal use (convention only)__private_attr: Private, name mangled to_ClassName__private_attr
โ ๏ธ Important: Python doesn't enforce true private accessโit's more about signaling intent to other developers!
Inheritance: Building Class Hierarchies ๐ณ
Inheritance allows you to create specialized classes that inherit attributes and methods from parent classes.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement speak()")
def introduce(self):
return f"I am {self.name}"
class Cat(Animal):
def speak(self):
return "Meow!"
def purr(self):
return f"{self.name} purrs contentedly"
class Dog(Animal):
def speak(self):
return "Woof!"
def fetch(self):
return f"{self.name} fetches the ball"
# Polymorphism in action
animals = [Cat("Whiskers"), Dog("Rex"), Cat("Mittens")]
for animal in animals:
print(f"{animal.introduce()}: {animal.speak()}")
INHERITANCE HIERARCHY
โโโโโโโโโโโโโโโ
โ Animal โ
โ (abstract) โ
โโโโโโโโฌโโโโโโโ
โ
โโโโโโโโดโโโโโโโ
โ โ
โโโโโโดโโโโโ โโโโโโดโโโโโ
โ Cat โ โ Dog โ
โ speak() โ โ speak() โ
โ purr() โ โ fetch() โ
โโโโโโโโโโโ โโโโโโโโโโโ
Multiple inheritance (use cautiously!):
class Flyer:
def fly(self):
return "Flying through the air!"
class Swimmer:
def swim(self):
return "Swimming in water!"
class Duck(Animal, Flyer, Swimmer):
def speak(self):
return "Quack!"
duck = Duck("Donald")
print(duck.speak()) # From Duck
print(duck.fly()) # From Flyer
print(duck.swim()) # From Swimmer
๐ง MRO Memory Trick: Python uses Method Resolution Order (MRO) to determine which method to call. Use ClassName.__mro__ to see the search order!
Polymorphism: One Interface, Many Forms ๐ญ
Polymorphism allows objects of different classes to be treated uniformly through a common interface.
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount):
pass
class CreditCardProcessor(PaymentProcessor):
def process_payment(self, amount):
return f"Processing ${amount} via Credit Card"
class PayPalProcessor(PaymentProcessor):
def process_payment(self, amount):
return f"Processing ${amount} via PayPal"
class CryptoProcessor(PaymentProcessor):
def process_payment(self, amount):
return f"Processing ${amount} via Cryptocurrency"
def checkout(processor: PaymentProcessor, amount: float):
"""Works with ANY payment processor!"""
return processor.process_payment(amount)
# All three work with the same function
print(checkout(CreditCardProcessor(), 99.99))
print(checkout(PayPalProcessor(), 149.50))
print(checkout(CryptoProcessor(), 299.00))
๐ก Duck typing: "If it walks like a duck and quacks like a duck, it's a duck." Python doesn't require explicit inheritanceโany object with the right methods will work!
SOLID Principles: Writing Clean OOP Code ๐
The SOLID principles are five guidelines for creating maintainable, flexible object-oriented software.
S - Single Responsibility Principle (SRP)
"A class should have only one reason to change."
โ Bad: Class doing too many things
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def save_to_database(self):
# Database logic
pass
def send_welcome_email(self):
# Email logic
pass
def generate_report(self):
# Report generation logic
pass
โ Good: Separate concerns
class User:
def __init__(self, name, email):
self.name = name
self.email = email
class UserRepository:
def save(self, user):
# Database logic only
pass
class EmailService:
def send_welcome_email(self, user):
# Email logic only
pass
class ReportGenerator:
def generate_user_report(self, user):
# Reporting logic only
pass
O - Open/Closed Principle (OCP)
"Classes should be open for extension, but closed for modification."
โ Good: Extend behavior without modifying existing code
class Discount(ABC):
@abstractmethod
def calculate(self, amount):
pass
class NoDiscount(Discount):
def calculate(self, amount):
return amount
class PercentageDiscount(Discount):
def __init__(self, percentage):
self.percentage = percentage
def calculate(self, amount):
return amount * (1 - self.percentage / 100)
class FixedDiscount(Discount):
def __init__(self, fixed_amount):
self.fixed_amount = fixed_amount
def calculate(self, amount):
return max(0, amount - self.fixed_amount)
# Add new discount types without changing existing code!
L - Liskov Substitution Principle (LSP)
"Derived classes must be substitutable for their base classes."
Subclasses should strengthen, not weaken, the parent class contract.
I - Interface Segregation Principle (ISP)
"Clients shouldn't be forced to depend on interfaces they don't use."
โ Good: Small, focused interfaces
class Readable(ABC):
@abstractmethod
def read(self):
pass
class Writable(ABC):
@abstractmethod
def write(self, data):
pass
class ReadOnlyFile(Readable):
def read(self):
return "Reading data..."
class ReadWriteFile(Readable, Writable):
def read(self):
return "Reading data..."
def write(self, data):
return f"Writing {data}..."
D - Dependency Inversion Principle (DIP)
"Depend on abstractions, not concretions."
โ Good: Inject dependencies
class Database(ABC):
@abstractmethod
def save(self, data):
pass
class MySQLDatabase(Database):
def save(self, data):
return f"Saving to MySQL: {data}"
class UserService:
def __init__(self, database: Database): # Depends on abstraction
self.database = database
def create_user(self, user_data):
return self.database.save(user_data)
# Easy to swap database implementations!
service = UserService(MySQLDatabase())
๐ง SOLID Memory Device: "Some Officers Love Ice-cream Daily"
- Single Responsibility
- Open/Closed
- Liskov Substitution
- Interface Segregation
- Dependency Inversion
Essential Design Patterns ๐จ
Design patterns are reusable solutions to common software design problems. Let's explore the most important ones!
Creational Patterns: Object Creation
1. Singleton Pattern ๐ฏ
Purpose: Ensure a class has only one instance and provide global access to it.
Use cases: Configuration managers, logging, database connections
class DatabaseConnection:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.connection = "Database connected!"
return cls._instance
def query(self, sql):
return f"Executing: {sql}"
# Always returns the same instance
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True - same object!
Thread-safe Singleton (better approach):
import threading
class ThreadSafeSingleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None: # Double-checked locking
cls._instance = super().__new__(cls)
return cls._instance
2. Factory Pattern ๐ญ
Purpose: Create objects without specifying exact classes.
from abc import ABC, abstractmethod
class Button(ABC):
@abstractmethod
def render(self):
pass
class WindowsButton(Button):
def render(self):
return "Rendering Windows-style button"
class MacButton(Button):
def render(self):
return "Rendering Mac-style button"
class ButtonFactory:
@staticmethod
def create_button(os_type):
if os_type == "Windows":
return WindowsButton()
elif os_type == "Mac":
return MacButton()
else:
raise ValueError(f"Unknown OS: {os_type}")
# Client code doesn't need to know specific classes
button = ButtonFactory.create_button("Windows")
print(button.render())
Structural Patterns: Object Composition
3. Decorator Pattern ๐
Purpose: Add behavior to objects dynamically without modifying their code.
class Coffee:
def cost(self):
return 5.0
def description(self):
return "Plain coffee"
class CoffeeDecorator:
def __init__(self, coffee):
self._coffee = coffee
def cost(self):
return self._coffee.cost()
def description(self):
return self._coffee.description()
class Milk(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 1.5
def description(self):
return self._coffee.description() + ", milk"
class Sugar(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.5
def description(self):
return self._coffee.description() + ", sugar"
# Build complex objects dynamically
coffee = Coffee()
coffee_with_milk = Milk(coffee)
coffee_with_milk_and_sugar = Sugar(coffee_with_milk)
print(coffee_with_milk_and_sugar.description()) # Plain coffee, milk, sugar
print(coffee_with_milk_and_sugar.cost()) # 7.0
4. Adapter Pattern ๐
Purpose: Make incompatible interfaces work together.
class EuropeanSocket:
def provide_240v(self):
return "240V European power"
class AmericanPlug:
def request_110v(self):
return "Requesting 110V American power"
class PowerAdapter:
def __init__(self, socket):
self.socket = socket
def request_110v(self):
power = self.socket.provide_240v()
return f"Adapting {power} to 110V"
# Use European socket with American device
euros_ocket = EuropeanSocket()
adapter = PowerAdapter(european_socket)
american_device = AmericanPlug()
print(adapter.request_110v()) # Works!
Behavioral Patterns: Object Interaction
5. Observer Pattern ๐
Purpose: Notify multiple objects about state changes (publish-subscribe).
class Subject:
def __init__(self):
self._observers = []
self._state = None
def attach(self, observer):
self._observers.append(observer)
def detach(self, observer):
self._observers.remove(observer)
def notify(self):
for observer in self._observers:
observer.update(self._state)
def set_state(self, state):
self._state = state
self.notify()
class Observer(ABC):
@abstractmethod
def update(self, state):
pass
class EmailAlert(Observer):
def update(self, state):
print(f"Email Alert: State changed to {state}")
class SMSAlert(Observer):
def update(self, state):
print(f"SMS Alert: State changed to {state}")
# Usage
subject = Subject()
email = EmailAlert()
sms = SMSAlert()
subject.attach(email)
subject.attach(sms)
subject.set_state("CRITICAL") # Both observers notified
6. Strategy Pattern ๐ฎ
Purpose: Define a family of algorithms and make them interchangeable.
class SortStrategy(ABC):
@abstractmethod
def sort(self, data):
pass
class BubbleSort(SortStrategy):
def sort(self, data):
return sorted(data) # Simplified
class QuickSort(SortStrategy):
def sort(self, data):
return sorted(data) # Simplified
class DataProcessor:
def __init__(self, strategy: SortStrategy):
self.strategy = strategy
def set_strategy(self, strategy: SortStrategy):
self.strategy = strategy
def process(self, data):
return self.strategy.sort(data)
# Change algorithm at runtime
processor = DataProcessor(BubbleSort())
print(processor.process([3, 1, 4, 1, 5]))
processor.set_strategy(QuickSort())
print(processor.process([3, 1, 4, 1, 5]))
DESIGN PATTERN CATEGORIES โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ DESIGN PATTERNS โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ โ โ ๐๏ธ CREATIONAL (Object Creation) โ โ โโ Singleton โ โ โโ Factory โ โ โโ Abstract Factory โ โ โโ Builder โ โ โ โ ๐ง STRUCTURAL (Object Composition) โ โ โโ Adapter โ โ โโ Decorator โ โ โโ Facade โ โ โโ Proxy โ โ โ โ ๐ฏ BEHAVIORAL (Object Interaction) โ โ โโ Observer โ โ โโ Strategy โ โ โโ Command โ โ โโ Iterator โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Real-World Examples ๐
Example 1: E-commerce Order System
Combining multiple OOP concepts and patterns:
from abc import ABC, abstractmethod
from datetime import datetime
from typing import List
# Strategy Pattern for payment
class PaymentMethod(ABC):
@abstractmethod
def process(self, amount: float) -> str:
pass
class CreditCard(PaymentMethod):
def process(self, amount: float) -> str:
return f"Charged ${amount} to credit card"
class PayPal(PaymentMethod):
def process(self, amount: float) -> str:
return f"Charged ${amount} via PayPal"
# Observer Pattern for notifications
class OrderObserver(ABC):
@abstractmethod
def notify(self, order):
pass
class EmailNotification(OrderObserver):
def notify(self, order):
print(f"Email: Order {order.order_id} placed for ${order.total}")
class SMSNotification(OrderObserver):
def notify(self, order):
print(f"SMS: Order {order.order_id} confirmed")
# Main business logic with encapsulation
class Order:
_order_counter = 1000 # Class variable for order IDs
def __init__(self, customer_name: str):
Order._order_counter += 1
self.order_id = Order._order_counter
self.customer_name = customer_name
self.__items = [] # Private
self.__observers: List[OrderObserver] = []
self.timestamp = datetime.now()
def add_item(self, item: str, price: float):
self.__items.append({"item": item, "price": price})
@property
def total(self) -> float:
return sum(item["price"] for item in self.__items)
def attach_observer(self, observer: OrderObserver):
self.__observers.append(observer)
def place_order(self, payment_method: PaymentMethod):
payment_result = payment_method.process(self.total)
print(payment_result)
# Notify all observers
for observer in self.__observers:
observer.notify(self)
return f"Order {self.order_id} placed successfully!"
# Usage
order = Order("Alice Johnson")
order.add_item("Laptop", 999.99)
order.add_item("Mouse", 29.99)
# Attach observers
order.attach_observer(EmailNotification())
order.attach_observer(SMSNotification())
# Process payment
result = order.place_order(CreditCard())
print(result)
Output:
Charged $1029.98 to credit card
Email: Order 1001 placed for $1029.98
SMS: Order 1001 confirmed
Order 1001 placed successfully!
Example 2: Plugin System with Abstract Base Classes
from abc import ABC, abstractmethod
from typing import Dict, Type
class Plugin(ABC):
@abstractmethod
def execute(self, data: str) -> str:
pass
@abstractmethod
def get_name(self) -> str:
pass
class UpperCasePlugin(Plugin):
def execute(self, data: str) -> str:
return data.upper()
def get_name(self) -> str:
return "UpperCase"
class ReversePlugin(Plugin):
def execute(self, data: str) -> str:
return data[::-1]
def get_name(self) -> str:
return "Reverse"
class PluginManager:
def __init__(self):
self._plugins: Dict[str, Plugin] = {}
def register(self, plugin: Plugin):
self._plugins[plugin.get_name()] = plugin
print(f"Registered plugin: {plugin.get_name()}")
def execute_plugin(self, plugin_name: str, data: str):
if plugin_name in self._plugins:
return self._plugins[plugin_name].execute(data)
return f"Plugin '{plugin_name}' not found"
# Usage
manager = PluginManager()
manager.register(UpperCasePlugin())
manager.register(ReversePlugin())
print(manager.execute_plugin("UpperCase", "hello")) # HELLO
print(manager.execute_plugin("Reverse", "hello")) # olleh
Example 3: Game Character System with Inheritance
class Character:
def __init__(self, name: str, health: int):
self.name = name
self._health = health
self._max_health = health
@property
def health(self):
return self._health
def take_damage(self, damage: int):
self._health = max(0, self._health - damage)
status = "alive" if self._health > 0 else "defeated"
return f"{self.name} took {damage} damage. HP: {self._health}/{self._max_health} ({status})"
def attack(self) -> int:
return 10 # Base attack
class Warrior(Character):
def __init__(self, name: str):
super().__init__(name, health=150)
self.armor = 20
def attack(self) -> int:
return 25 # Warriors hit harder
def take_damage(self, damage: int):
reduced_damage = max(0, damage - self.armor)
return super().take_damage(reduced_damage)
def shield_bash(self):
return f"{self.name} performs a shield bash for 30 damage!"
class Mage(Character):
def __init__(self, name: str):
super().__init__(name, health=80)
self.mana = 100
def attack(self) -> int:
return 15
def cast_spell(self, spell_name: str):
if self.mana >= 20:
self.mana -= 20
return f"{self.name} casts {spell_name}! Mana: {self.mana}/100"
return f"{self.name} has insufficient mana!"
# Polymorphism in action
def battle(attacker: Character, defender: Character):
damage = attacker.attack()
result = defender.take_damage(damage)
return f"{attacker.name} attacks! {result}"
warrior = Warrior("Thorin")
mage = Mage("Gandalf")
print(battle(warrior, mage))
print(mage.cast_spell("Fireball"))
print(warrior.shield_bash())
Example 4: Document Processing Pipeline
Demonstrating Decorator pattern and method chaining:
class Document:
def __init__(self, content: str):
self.content = content
def get_content(self) -> str:
return self.content
class DocumentProcessor(ABC):
def __init__(self, document):
self._document = document
@abstractmethod
def get_content(self) -> str:
pass
class RemoveWhitespace(DocumentProcessor):
def get_content(self) -> str:
content = self._document.get_content()
return ' '.join(content.split())
class ToUpperCase(DocumentProcessor):
def get_content(self) -> str:
content = self._document.get_content()
return content.upper()
class AddTimestamp(DocumentProcessor):
def get_content(self) -> str:
content = self._document.get_content()
return f"[{datetime.now()}] {content}"
# Chain decorators
doc = Document(" hello world ")
processed = AddTimestamp(ToUpperCase(RemoveWhitespace(doc)))
print(processed.get_content())
# Output: [2024-01-15 10:30:45.123456] HELLO WORLD
๐ค Did you know? The Gang of Four (GoF) book "Design Patterns" published in 1994 documented 23 design patterns that are still relevant today. Many modern frameworks are built on these foundational patterns!
Common Mistakes & How to Avoid Them โ ๏ธ
Mistake 1: God Objects (Violating SRP)
โ Wrong: One class doing everything
class Application:
def __init__(self):
self.users = []
self.products = []
def add_user(self, user): pass
def send_email(self, to, subject): pass
def process_payment(self, amount): pass
def generate_report(self): pass
def log_activity(self, message): pass
# ... 50 more methods
โ Right: Separate concerns
class UserRepository:
def add_user(self, user): pass
class EmailService:
def send(self, to, subject): pass
class PaymentProcessor:
def process(self, amount): pass
Mistake 2: Improper Use of Inheritance
โ Wrong: Inheritance for code reuse (should use composition)
class ArrayList: # Has list functionality
def add(self, item): pass
class Stack(ArrayList): # Inherits list methods
def push(self, item):
self.add(item) # But exposes ALL list methods!
โ Right: Composition over inheritance
class Stack:
def __init__(self):
self._items = [] # Composition
def push(self, item):
self._items.append(item)
def pop(self):
return self._items.pop()
# Only expose stack-specific interface
Mistake 3: Forgetting self in Methods
โ Wrong:
class Counter:
def __init__(self):
self.count = 0
def increment(): # Missing self!
self.count += 1 # Error!
โ Right:
class Counter:
def __init__(self):
self.count = 0
def increment(self): # Include self
self.count += 1
Mistake 4: Mutable Default Arguments
โ Wrong:
class ShoppingCart:
def __init__(self, items=[]): # DANGER!
self.items = items
cart1 = ShoppingCart()
cart1.items.append("apple")
cart2 = ShoppingCart() # Shares items with cart1!
print(cart2.items) # ['apple'] - unexpected!
โ Right:
class ShoppingCart:
def __init__(self, items=None):
self.items = items if items is not None else []
Mistake 5: Overusing Singletons
Problem: Singletons are essentially global state, making testing difficult and creating hidden dependencies.
โ Better: Use dependency injection instead:
class Service:
def __init__(self, config): # Inject dependencies
self.config = config
# In tests, inject mock config
test_service = Service(MockConfig())
Mistake 6: Not Using Abstract Base Classes
โ Wrong: Forgetting to implement required methods
class Animal:
def speak(self):
raise NotImplementedError()
class Dog(Animal):
pass # Forgot to implement speak()!
dog = Dog() # No error until runtime
dog.speak() # Error only when called!
โ Right: Use ABC for compile-time checking
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
class Dog(Animal): # Error immediately if speak() missing
def speak(self):
return "Woof!"
๐ง Try this: Take an existing procedural program you've written and refactor it using OOP principles. Identify natural classes, apply encapsulation, and see how it improves maintainability!
Key Takeaways ๐
โ Classes and objects provide structure for organizing related data and behavior
โ The four OOP pillars (Encapsulation, Abstraction, Inheritance, Polymorphism) work together to create flexible, maintainable code
โ SOLID principles guide you toward clean, professional OOP design:
- Single Responsibility: One class, one purpose
- Open/Closed: Extend, don't modify
- Liskov Substitution: Subclasses must honor parent contracts
- Interface Segregation: Many small interfaces beat one large one
- Dependency Inversion: Depend on abstractions
โ Design patterns provide proven solutions to recurring problems:
- Creational (Singleton, Factory): Object creation
- Structural (Decorator, Adapter): Object composition
- Behavioral (Observer, Strategy): Object interaction
โ Composition over inheritance: Prefer "has-a" relationships over "is-a" when possible
โ Abstract base classes enforce contracts and catch errors early
โ Properties and name mangling help maintain encapsulation in Python
โ Avoid common pitfalls: God objects, mutable defaults, excessive inheritance, Singleton overuse
๐ Further Study
Essential resources for deepening your OOP & patterns knowledge:
Python's Official Documentation on Classes: https://docs.python.org/3/tutorial/classes.html - Comprehensive guide to Python's OOP features with excellent examples
Refactoring.Guru Design Patterns: https://refactoring.guru/design-patterns/python - Interactive catalog of design patterns with Python implementations, visual diagrams, and real-world use cases
Real Python OOP Tutorial: https://realpython.com/python3-object-oriented-programming/ - In-depth tutorial covering advanced OOP concepts, best practices, and Python-specific idioms
๐ Quick Reference Card
| Concept | Key Points | Python Syntax |
|---|---|---|
| Class Definition | Blueprint for objects | class MyClass: |
| Constructor | Initialize instances | def __init__(self, params): |
| Encapsulation | Private: __ prefix | self.__private_attr |
| Inheritance | Extend parent class | class Child(Parent): |
| Abstract Class | Cannot instantiate | class Base(ABC): |
| Abstract Method | Must implement in subclass | @abstractmethod |
| Property | Getter/setter methods | @property |
| Singleton | One instance only | Override __new__ |
| Factory | Create objects dynamically | Static method returns instances |
| Decorator Pattern | Add behavior dynamically | Wrapper classes |
| Observer Pattern | Notify on state change | attach/notify methods |
| Strategy Pattern | Interchangeable algorithms | Inject behavior via constructor |
SOLID Quick Reference:
- S: One class = one responsibility
- O: Extend, don't modify
- L: Subclasses honor parent contracts
- I: Small, focused interfaces
- D: Depend on abstractions
When to Use Which Pattern:
- Need one instance? โ Singleton
- Need flexible object creation? โ Factory
- Need to add features dynamically? โ Decorator
- Need to notify multiple objects? โ Observer
- Need interchangeable algorithms? โ Strategy
- Need to make incompatible interfaces work? โ Adapter