Table of Contents
- Introduction
- E-commerce Systems: Products, Orders, and Customers
- 2.1 Scenario
- 2.2 Key OOP Concepts: Encapsulation, Inheritance, Polymorphism
- 2.3 Implementation with Code
- Banking Applications: Accounts and Transactions
- 3.1 Scenario
- 3.2 Key OOP Concepts: Encapsulation, Inheritance
- 3.3 Implementation with Code
- Game Development: Characters and Items
- 4.1 Scenario
- 4.2 Key OOP Concepts: Inheritance, Polymorphism
- 4.3 Implementation with Code
- Data Analysis: Datasets and Models
- 5.1 Scenario
- 5.2 Key OOP Concepts: Abstraction, Inheritance
- 5.3 Implementation with Code
- Content Management Systems (CMS): Users and Posts
- 5.1 Scenario
- 5.2 Key OOP Concepts: Encapsulation, Composition
- 5.3 Implementation with Code
- Conclusion
- References
1. E-commerce Systems: Products, Orders, and Customers
1.1 Scenario
An e-commerce platform needs to manage products (e.g., books, electronics), track customer orders, and calculate totals. Products may have different attributes (e.g., physical products have weight; digital products have download links). Orders must link customers to products and compute costs.
1.2 Key OOP Concepts
- Encapsulation: Hide sensitive data (e.g., product stock) and expose controlled access (e.g.,
update_stock()method to prevent negative stock). - Inheritance: Use a base
Productclass with shared attributes (name, price) and subclasses likePhysicalProductandDigitalProductfor type-specific logic. - Polymorphism: Define a common method (e.g.,
calculate_shipping()) that behaves differently forPhysicalProduct(uses weight) andDigitalProduct(returns $0).
1.3 Implementation with Code
Step 1: Base Product Class
class Product:
def __init__(self, name: str, price: float, stock: int = 0):
self.name = name # Public attribute: product name
self.price = price # Public attribute: product price
self._stock = stock # Private attribute: stock (encapsulated)
def get_stock(self) -> int:
"""Return current stock (controlled access to private _stock)."""
return self._stock
def update_stock(self, quantity: int) -> None:
"""Update stock (prevents negative stock via encapsulation)."""
if quantity < 0 and abs(quantity) > self._stock:
raise ValueError("Insufficient stock.")
self._stock += quantity
def calculate_discount(self) -> float:
"""Base discount logic (overridden by subclasses)."""
return self.price * 0.05 # 5% discount by default
Step 2: Subclasses for Product Types (Inheritance)
class PhysicalProduct(Product):
def __init__(self, name: str, price: float, weight_kg: float, stock: int = 0):
super().__init__(name, price, stock)
self.weight_kg = weight_kg # Type-specific attribute
def calculate_shipping(self, distance_km: float) -> float:
"""Calculate shipping cost based on weight and distance."""
return self.weight_kg * distance_km * 0.1 # $0.1 per kg-km
def calculate_discount(self) -> float:
"""Override: Physical products get 10% discount."""
return self.price * 0.10
class DigitalProduct(Product):
def __init__(self, name: str, price: float, download_link: str, stock: int = 0):
super().__init__(name, price, stock)
self.download_link = download_link # Type-specific attribute
def calculate_shipping(self, distance_km: float = 0) -> float:
"""Digital products have $0 shipping (polymorphism)."""
return 0.0
def calculate_discount(self) -> float:
"""Override: Digital products get 15% discount."""
return self.price * 0.15
Step 3: Order Class (Composition)
An order “has-a” customer and “contains” products (composition):
class Customer:
def __init__(self, name: str, email: str):
self.name = name
self.email = email
class Order:
def __init__(self, customer: Customer):
self.customer = customer # Composition: Order "has-a" Customer
self.products = [] # List of Product objects
self.status = "pending"
def add_product(self, product: Product, quantity: int) -> None:
"""Add a product to the order (check stock first)."""
if product.get_stock() < quantity:
raise ValueError(f"Not enough stock for {product.name}.")
self.products.append((product, quantity))
product.update_stock(-quantity) # Reduce stock
def calculate_total(self, distance_km: float = 0) -> float:
"""Calculate total cost including product prices and shipping."""
product_total = sum(p.price * q for p, q in self.products)
shipping_total = sum(p.calculate_shipping(distance_km) * q for p, q in self.products)
discount_total = sum(p.calculate_discount() * q for p, q in self.products)
return product_total + shipping_total - discount_total
def confirm(self) -> None:
"""Mark order as confirmed."""
self.status = "confirmed"
How It Works
- Encapsulation:
_stockis private; onlyupdate_stock()modifies it, preventing invalid stock levels. - Inheritance:
PhysicalProductandDigitalProductreuseProductlogic but add type-specific attributes/methods. - Polymorphism:
calculate_shipping()andcalculate_discount()behave differently for each product type, but theOrderclass calls them uniformly.
2. Banking Applications: Accounts and Transactions
2.1 Scenario
A bank needs to manage customer accounts (e.g., savings, checking), process deposits/withdrawals, and track transactions. Savings accounts earn interest, while checking accounts may charge fees for overdrafts.
2.2 Key OOP Concepts
- Encapsulation: Protect account balance with private attributes; only allow modification via
deposit()/withdraw(). - Inheritance: Use a base
Accountclass with shared logic, and subclasses for account types. - Abstraction: Define a common interface (e.g.,
get_balance()) for all accounts, hiding implementation details.
2.3 Implementation with Code
Step 1: Base Account Class
from datetime import datetime
class Account:
def __init__(self, account_number: str, balance: float = 0.0):
self.account_number = account_number
self._balance = balance # Private: balance can’t be modified directly
self.transactions = [] # List of Transaction objects
def get_balance(self) -> float:
"""Return current balance (encapsulated access)."""
return self._balance
def deposit(self, amount: float) -> None:
"""Deposit funds (validate positive amount)."""
if amount <= 0:
raise ValueError("Deposit amount must be positive.")
self._balance += amount
self.transactions.append(Transaction("deposit", amount))
def withdraw(self, amount: float) -> None:
"""Withdraw funds (base class: no overdraft allowed)."""
if amount <= 0:
raise ValueError("Withdrawal amount must be positive.")
if amount > self._balance:
raise ValueError("Insufficient funds.")
self._balance -= amount
self.transactions.append(Transaction("withdrawal", amount))
Step 2: Subclasses for Account Types (Inheritance)
class SavingsAccount(Account):
def __init__(self, account_number: str, interest_rate: float = 0.02, balance: float = 0.0):
super().__init__(account_number, balance)
self.interest_rate = interest_rate # Annual interest rate (e.g., 2%)
def apply_interest(self) -> None:
"""Add interest to the account (e.g., monthly)."""
interest = self._balance * (self.interest_rate / 12) # Monthly interest
self.deposit(interest) # Reuse deposit() method
self.transactions.append(Transaction("interest", interest))
class CheckingAccount(Account):
OVERDRAFT_FEE = 35.0 # Class-level constant
def withdraw(self, amount: float) -> None:
"""Override: Allow overdraft but charge a fee."""
if amount > self._balance:
# Charge fee and allow withdrawal (if fee + amount <= balance + fee?)
self._balance -= self.OVERDRAFT_FEE
self.transactions.append(Transaction("overdraft_fee", -self.OVERDRAFT_FEE))
super().withdraw(amount) # Call parent withdraw() to deduct amount
Step 3: Transaction Class
class Transaction:
def __init__(self, transaction_type: str, amount: float):
self.type = transaction_type # e.g., "deposit", "withdrawal"
self.amount = amount
self.timestamp = datetime.now() # Auto-record time of transaction
def __str__(self) -> str:
return f"{self.timestamp}: {self.type} - ${self.amount:.2f}"
How It Works
- Encapsulation:
_balanceis private; onlydeposit()/withdraw()modify it, ensuring valid transactions. - Inheritance:
SavingsAccountandCheckingAccountinherit core logic fromAccountbut add features like interest or overdraft fees. - Abstraction: All accounts implement
get_balance(), so the bank can treat them uniformly (e.g., in reports).
3. Game Development: Characters and Items
3.1 Scenario
A role-playing game (RPG) needs playable characters (e.g., warriors, mages) with unique abilities, and items (e.g., swords, potions) that modify stats. Warriors侧重 strength, mages侧重 magic.
3.2 Key OOP Concepts
- Inheritance:
WarriorandMageinherit from a baseCharacterclass. - Polymorphism: Characters override
attack()to use class-specific abilities. - Composition: Characters “have” items (e.g., a warrior “has-a” sword).
3.3 Implementation with Code
Step 1: Base Character Class
class Character:
def __init__(self, name: str, health: int = 100):
self.name = name
self.health = health
self.inventory = [] # List of Item objects
self.level = 1
def attack(self, target: "Character") -> None:
"""Base attack (overridden by subclasses)."""
damage = 10 # Base damage
target.take_damage(damage)
print(f"{self.name} attacks {target.name} for {damage} damage!")
def take_damage(self, damage: int) -> None:
"""Reduce health by damage (minimum 0)."""
self.health = max(0, self.health - damage)
if self.health == 0:
print(f"{self.name} has been defeated!")
def add_item(self, item: "Item") -> None:
"""Add an item to inventory."""
self.inventory.append(item)
item.apply_buff(self) # Item modifies character stats
Step 2: Character Subclasses (Polymorphism)
class Warrior(Character):
def __init__(self, name: str):
super().__init__(name)
self.strength = 15 # Warrior-specific stat
def attack(self, target: Character) -> None:
"""Warrior attack: uses strength for higher damage."""
damage = self.strength * 2 # Strength-based damage
target.take_damage(damage)
print(f"{self.name} slashes {target.name} for {damage} damage!")
class Mage(Character):
def __init__(self, name: str):
super().__init__(name)
self.mana = 50 # Mage-specific stat
def attack(self, target: Character) -> None:
"""Mage attack: uses mana for magic damage."""
if self.mana < 10:
print(f"{self.name} has no mana!")
return
damage = 15 + (self.mana // 10) # Mana-based damage
self.mana -= 10
target.take_damage(damage)
print(f"{self.name} casts a fireball at {target.name} for {damage} damage!")
Step 3: Item Class and Subclasses (Composition)
class Item:
def apply_buff(self, character: Character) -> None:
"""Base method: override to modify character stats."""
pass
class Sword(Item):
def apply_buff(self, character: Character) -> None:
"""Increase warrior's strength."""
if isinstance(character, Warrior):
character.strength += 5
print(f"{character.name} equips a sword! Strength +5.")
class Staff(Item):
def apply_buff(self, character: Character) -> None:
"""Increase mage's mana."""
if isinstance(character, Mage):
character.mana += 20
print(f"{character.name} equips a staff! Mana +20.")
class HealthPotion(Item):
def apply_buff(self, character: Character) -> None:
"""Restore health."""
character.health = min(100, character.health + 30)
print(f"{character.name} drinks a potion! Health +30.")
How It Works
- Polymorphism:
Warrior.attack()andMage.attack()override the base method, enabling unique combat styles. - Composition:
Character“has” items, and items modify character stats viaapply_buff(). - Inheritance: All characters share core logic (health, inventory) but add class-specific stats (strength/mana).
4. Data Analysis: Datasets and Models
4.1 Scenario
A data science team needs to load, clean, and analyze datasets (e.g., CSV files, time-series data) and train machine learning models to make predictions.
4.2 Key OOP Concepts
- Abstraction: Define a
Datasetinterface with common methods (load(),clean()). - Inheritance:
TimeSeriesDatasetinherits fromDatasetand adds time-specific logic. - Composition: A
Model“uses” aDatasetfor training.
4.3 Implementation with Code
Step 1: Base Dataset Class
import pandas as pd
class Dataset:
def __init__(self, file_path: str):
self.file_path = file_path
self.data = None # Stores raw data (e.g., pandas DataFrame)
self.cleaned_data = None # Stores cleaned data
def load(self) -> None:
"""Load data from file (base method for CSV)."""
self.data = pd.read_csv(self.file_path)
print(f"Loaded {self.file_path} with {len(self.data)} rows.")
def clean(self) -> None:
"""Basic cleaning: drop NaNs and duplicates."""
self.cleaned_data = self.data.dropna().drop_duplicates()
print(f"Cleaned data: {len(self.cleaned_data)} rows remaining.")
def describe(self) -> None:
"""Show summary statistics."""
if self.cleaned_data is None:
self.clean()
print(self.cleaned_data.describe())
Step 2: TimeSeriesDataset Subclass (Inheritance)
from pandas import DatetimeIndex
class TimeSeriesDataset(Dataset):
def load(self) -> None:
"""Override: Load CSV and parse datetime column."""
super().load() # Reuse parent load()
self.data["timestamp"] = pd.to_datetime(self.data["timestamp"])
self.data.set_index("timestamp", inplace=True) # Set time as index
def resample(self, freq: str = "D") -> pd.DataFrame:
"""Resample time-series data (e.g., daily averages)."""
if not isinstance(self.data.index, DatetimeIndex):
raise ValueError("Data is not time-series.")
return self.data.resample(freq).mean()
Step 3: Model Class (Composition)
from sklearn.linear_model import LinearRegression
class Model:
def __init__(self, dataset: Dataset):
self.dataset = dataset # Composition: Model "uses" a Dataset
self.model = LinearRegression() # Example: scikit-learn model
self.trained = False
def train(self, target_column: str) -> None:
"""Train model on cleaned dataset."""
if self.dataset.cleaned_data is None:
self.dataset.clean()
X = self.dataset.cleaned_data.drop(columns=[target_column])
y = self.dataset.cleaned_data[target_column]
self.model.fit(X, y)
self.trained = True
print("Model trained!")
def predict(self, new_data: pd.DataFrame) -> list:
"""Predict using trained model."""
if not self.trained:
raise ValueError("Train the model first!")
return self.model.predict(new_data)
How It Works
- Abstraction:
Datasetdefines a common interface (load(),clean()), soModelcan work with any dataset type. - Inheritance:
TimeSeriesDatasetadds time-specific logic (resampling) while reusingDatasetcleaning. - Composition:
Modelrelies on aDatasetfor data, decoupling data preparation from model training.
5. Content Management Systems (CMS): Users and Posts
5.1 Scenario
A CMS powers a blog, with users (editors, admins) creating/editing posts. Admins can delete posts; editors can only edit their own posts.
5.2 Key OOP Concepts
- Encapsulation: Restrict post editing via role checks (e.g., only admins delete).
- Composition: A
Post“has-an” author (aUserobject). - Polymorphism: Users with different roles override
can_edit()to enforce permissions.
5.3 Implementation with Code
Step 1: User Class and Subclasses (Polymorphism)
class User:
def __init__(self, name: str, user_id: int):
self.name = name
self.user_id = user_id
def can_edit(self, post: "Post") -> bool:
"""Base: Users can edit their own posts."""
return post.author.user_id == self.user_id
class Editor(User):
def can_edit(self, post: "Post") -> bool:
"""Editors can edit any post (override)."""
return True
class Admin(User):
def can_delete(self, post: "Post") -> bool:
"""Admins can delete any post."""
return True
Step 2: Post Class (Composition)
from datetime import datetime
class Post:
def __init__(self, title: str, content: str, author: User):
self.title = title
self.content = content
self.author = author # Composition: Post "has-an" author (User)
self.created_at = datetime.now()
self.updated_at = self.created_at
self.published = False
def edit(self, new_content: str, editor: User) -> None:
"""Edit post if editor has permission."""
if editor.can_edit(self):
self.content = new_content
self.updated_at = datetime.now()
print(f"Post '{self.title}' edited by {editor.name}.")
else:
raise PermissionError(f"{editor.name} cannot edit this post.")
def publish(self, editor: User) -> None:
"""Publish post (requires edit permission)."""
if editor.can_edit(self):
self.published = True
print(f"Post '{self.title}' published!")
How It Works
- Encapsulation:
edit()andpublish()check permissions viacan_edit(), preventing unauthorized changes. - Composition:
Postlinks to aUser(author), enabling role-based access control. - Polymorphism:
EditorandAdminoverridecan_edit()to grant broader permissions than regular users.
Conclusion
OOP is not just a theoretical concept—it’s the backbone of real-world Python applications. By modeling entities as classes, leveraging inheritance for reuse, and using polymorphism for flexibility, developers build systems that are modular, scalable, and easy to maintain.
From e-commerce platforms managing products to games with dynamic characters, OOP aligns code with how humans naturally think about problems. The examples above show how encapsulation protects data, inheritance reduces redundancy, and polymorphism enables adaptability—skills that will make you a more effective Python developer.
References
- Python Official Documentation: Classes
- Fluent Python by Luciano Ramalho (O’Reilly Media)
- Real Python: Object-Oriented Programming in Python
- Django (CMS framework): Django Models
- Pandas (data analysis): DataFrame Objects