OOP: How I Stopped Writing Spaghetti Code and Started Building Legos
Six months into my job at Amazon, my tech lead pulled me aside after a code review. "Maya," she said, looking at my 800-line function, "this looks like you threw all your code into a blender and hit puree." That function did everything - handled payments, sent emails, updated databases, probably ordered pizza too.
That was my introduction to why Object-Oriented Programming exists. Let me share how OOP transformed my code from digital spaghetti into organized, maintainable architecture.
Table Of Contents
- The 800-Line Monster
- OOP: The Digital IKEA Approach
- The Four Pillars: My Coffee Shop Analogy
- Real-World Example: Building a Coffee Shop System
- Common OOP Mistakes (I've Made Them All)
- Design Patterns: The Greatest Hits
- OOP in Different Languages
- When NOT to Use OOP
- Final Thoughts: Objects Changed My Career
The 800-Line Monster
Here's what my pre-OOP code looked like (simplified, but the horror was real):
# The nightmare function that did EVERYTHING
def process_user_payment(user_email, amount, payment_method, card_number,
expiry, cvv, billing_address, item_ids, quantities):
# Validate user (50 lines of validation)
if '@' not in user_email:
return {'error': 'Invalid email'}
# Validate payment (100 lines)
if len(card_number) != 16:
return {'error': 'Invalid card'}
# Calculate totals (150 lines)
subtotal = 0
for i in range(len(item_ids)):
item_price = get_item_price(item_ids[i])
subtotal += item_price * quantities[i]
# Process payment (200 lines)
# Send confirmation email (100 lines)
# Update inventory (150 lines)
# Generate receipt (50 lines)
# By line 800, I'd forgotten what this function was supposed to do
My mentor took one look and said, "Let's talk about objects."
OOP: The Digital IKEA Approach
Object-Oriented Programming is like building with IKEA furniture. Instead of one massive, incomprehensible piece, you get:
- Objects: Individual furniture pieces (desk, chair, lamp)
- Classes: The instruction manual for building each piece
- Methods: What each piece can do (desk.holdLaptop(), chair.spin())
- Properties: What each piece has (desk.height, chair.color)
# Instead of one giant function, we create specialized objects
class User:
def __init__(self, email, name):
self.email = email
self.name = name
self.orders = []
def is_valid_email(self):
return '@' in self.email and '.' in self.email
class PaymentProcessor:
def __init__(self):
pass
def validate_card(self, card_number):
return len(card_number) == 16 and card_number.isdigit()
def process_payment(self, amount, card_info):
# Handle payment logic
pass
class Order:
def __init__(self, user, items):
self.user = user
self.items = items
self.total = self.calculate_total()
def calculate_total(self):
return sum(item.price * item.quantity for item in self.items)
The Four Pillars: My Coffee Shop Analogy
Encapsulation: The Espresso Machine
You don't need to know how an espresso machine works internally. You just press a button and get coffee. That's encapsulation - hiding complexity behind a simple interface.
class EspressoMachine {
#waterTemperature = 0; // Private property
#pressure = 0; // Private property
constructor() {
this.#heatWater();
this.#buildPressure();
}
#heatWater() { // Private method
this.#waterTemperature = 195;
}
#buildPressure() { // Private method
this.#pressure = 9;
}
makeEspresso() { // Public method
// Complex internal process hidden from user
return "Perfect espresso shot ☕";
}
}
const machine = new EspressoMachine();
console.log(machine.makeEspresso()); // Works!
// console.log(machine.#waterTemperature); // Error! Can't access private
Inheritance: The Coffee Family Tree
Different drinks share common properties but have their own specialties:
class Beverage:
def __init__(self, name, temperature):
self.name = name
self.temperature = temperature
self.ingredients = []
def add_ingredient(self, ingredient):
self.ingredients.append(ingredient)
def serve(self):
return f"Serving {self.temperature} {self.name}"
class Coffee(Beverage): # Inherits from Beverage
def __init__(self, name, roast_level="medium"):
super().__init__(name, "hot") # Call parent constructor
self.roast_level = roast_level
self.add_ingredient("coffee beans")
def add_caffeine_warning(self): # Coffee-specific method
return "⚠️ Contains caffeine"
class BubbleTea(Beverage): # Also inherits from Beverage
def __init__(self, flavor, sweetness=50):
super().__init__(f"{flavor} bubble tea", "cold")
self.sweetness = sweetness
self.add_ingredient("tea")
self.add_ingredient("tapioca pearls")
def adjust_sweetness(self, level): # BubbleTea-specific method
self.sweetness = level
# Using inheritance
my_latte = Coffee("Latte")
print(my_latte.serve()) # "Serving hot Latte" (inherited method)
print(my_latte.add_caffeine_warning()) # Coffee-specific method
my_taro = BubbleTea("Taro", 25)
print(my_taro.serve()) # "Serving cold Taro bubble tea" (inherited method)
my_taro.adjust_sweetness(50) # BubbleTea-specific method
Polymorphism: The Universal Remote
Different objects can respond to the same action in their own way, like how different devices respond to "power on":
// Java example from my Microsoft days
interface Drawable {
void draw();
}
class Circle implements Drawable {
public void draw() {
System.out.println("Drawing a circle ⭕");
}
}
class Square implements Drawable {
public void draw() {
System.out.println("Drawing a square ⬜");
}
}
class Triangle implements Drawable {
public void draw() {
System.out.println("Drawing a triangle 🔺");
}
}
// Polymorphism in action
public void drawShapes(Drawable[] shapes) {
for (Drawable shape : shapes) {
shape.draw(); // Each shape draws itself differently
}
}
Abstraction: The Menu vs The Kitchen
You see the menu (abstraction), not the complex kitchen operations:
// Abstract class - can't be instantiated directly
abstract class PaymentMethod {
abstract processPayment(amount: number): Promise<boolean>;
abstract getTransactionFee(): number;
// Concrete method available to all implementations
formatAmount(amount: number): string {
return `$${amount.toFixed(2)}`;
}
}
class CreditCard extends PaymentMethod {
processPayment(amount: number): Promise<boolean> {
// Credit card specific logic
console.log(`Processing credit card payment: ${this.formatAmount(amount)}`);
return Promise.resolve(true);
}
getTransactionFee(): number {
return 0.029; // 2.9%
}
}
class PayPal extends PaymentMethod {
processPayment(amount: number): Promise<boolean> {
// PayPal specific logic
console.log(`Processing PayPal payment: ${this.formatAmount(amount)}`);
return Promise.resolve(true);
}
getTransactionFee(): number {
return 0.034; // 3.4%
}
}
Real-World Example: Building a Coffee Shop System
Let me show you how I'd build a coffee shop management system using OOP principles:
from datetime import datetime
from enum import Enum
class DrinkSize(Enum):
SMALL = "small"
MEDIUM = "medium"
LARGE = "large"
class Drink:
def __init__(self, name, base_price):
self.name = name
self.base_price = base_price
self.customizations = []
def add_customization(self, customization, price_modifier=0):
self.customizations.append({
'name': customization,
'price': price_modifier
})
def get_total_price(self, size):
size_multiplier = {
DrinkSize.SMALL: 0.8,
DrinkSize.MEDIUM: 1.0,
DrinkSize.LARGE: 1.2
}
base = self.base_price * size_multiplier[size]
customization_cost = sum(c['price'] for c in self.customizations)
return base + customization_cost
class Customer:
def __init__(self, name, email):
self.name = name
self.email = email
self.loyalty_points = 0
self.order_history = []
def earn_points(self, amount_spent):
self.loyalty_points += int(amount_spent)
def redeem_points(self, points):
if self.loyalty_points >= points:
self.loyalty_points -= points
return True
return False
class Order:
def __init__(self, customer):
self.customer = customer
self.items = []
self.timestamp = datetime.now()
self.status = "pending"
def add_item(self, drink, size, quantity=1):
item = {
'drink': drink,
'size': size,
'quantity': quantity,
'price': drink.get_total_price(size) * quantity
}
self.items.append(item)
def get_total(self):
return sum(item['price'] for item in self.items)
def complete_order(self):
total = self.get_total()
self.customer.earn_points(total)
self.customer.order_history.append(self)
self.status = "completed"
return total
class CoffeeShop:
def __init__(self, name):
self.name = name
self.menu = {}
self.customers = {}
self.daily_sales = 0
def add_drink_to_menu(self, drink):
self.menu[drink.name] = drink
def register_customer(self, customer):
self.customers[customer.email] = customer
def create_order(self, customer_email):
customer = self.customers.get(customer_email)
if customer:
return Order(customer)
return None
def process_order(self, order):
total = order.complete_order()
self.daily_sales += total
return total
# Using the system
shop = CoffeeShop("Maya's Byte-sized Brews")
# Create drinks
latte = Drink("Latte", 4.50)
latte.add_customization("Extra shot", 0.75)
latte.add_customization("Oat milk", 0.50)
bubble_tea = Drink("Taro Bubble Tea", 5.50)
bubble_tea.add_customization("Extra pearls", 0.50)
# Add to menu
shop.add_drink_to_menu(latte)
shop.add_drink_to_menu(bubble_tea)
# Register customer
maya = Customer("Maya Chen", "maya@example.com")
shop.register_customer(maya)
# Create and process order
order = shop.create_order("maya@example.com")
order.add_item(latte, DrinkSize.LARGE)
order.add_item(bubble_tea, DrinkSize.MEDIUM)
total = shop.process_order(order)
print(f"Order total: ${total:.2f}")
print(f"Maya's loyalty points: {maya.loyalty_points}")
Common OOP Mistakes (I've Made Them All)
God Objects
Creating objects that do everything:
# Bad - one class does everything
class UserManagerPaymentProcessorEmailSenderInventoryTracker:
def create_user(self): pass
def delete_user(self): pass
def process_payment(self): pass
def send_email(self): pass
def update_inventory(self): pass
# ... 50 more methods
# Good - separate concerns
class UserManager:
def create_user(self): pass
def delete_user(self): pass
class PaymentProcessor:
def process_payment(self): pass
class EmailService:
def send_email(self): pass
Deep Inheritance Chains
// Bad - too many levels
class Animal {}
class Mammal extends Animal {}
class Primate extends Mammal {}
class Ape extends Primate {}
class Human extends Ape {}
class Developer extends Human {}
class JavaScriptDeveloper extends Developer {}
class ReactDeveloper extends JavaScriptDeveloper {}
// Where does it end?!
// Good - prefer composition
class Developer {
constructor(skills, languages) {
this.skills = skills;
this.languages = languages;
}
}
const maya = new Developer(
['React', 'Node.js'],
['JavaScript', 'Python', 'TypeScript']
);
Design Patterns: The Greatest Hits
Singleton (Use Sparingly)
class DatabaseConnection:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.connected = False
return cls._instance
def connect(self):
if not self.connected:
print("Connecting to database...")
self.connected = True
# Always returns the same instance
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True
Factory Pattern
class CoffeeFactory {
static createDrink(type, options = {}) {
switch(type) {
case 'espresso':
return new Espresso(options);
case 'latte':
return new Latte(options);
case 'americano':
return new Americano(options);
default:
throw new Error(`Unknown drink type: ${type}`);
}
}
}
// Usage
const myDrink = CoffeeFactory.createDrink('latte', { size: 'large' });
Observer Pattern
class BubbleTeaShop:
def __init__(self):
self.customers = []
self.new_flavor = None
def add_customer(self, customer):
self.customers.append(customer)
def remove_customer(self, customer):
self.customers.remove(customer)
def notify_customers(self):
for customer in self.customers:
customer.update(self.new_flavor)
def add_new_flavor(self, flavor):
self.new_flavor = flavor
self.notify_customers()
class Customer:
def __init__(self, name):
self.name = name
def update(self, new_flavor):
print(f"{self.name} notified: New flavor '{new_flavor}' available!")
OOP in Different Languages
Python: Clean and Readable
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance # Protected
self.__transactions = [] # Private
@property
def balance(self):
return self._balance
def deposit(self, amount):
if amount > 0:
self._balance += amount
self.__transactions.append(f"Deposit: +${amount}")
def withdraw(self, amount):
if 0 < amount <= self._balance:
self._balance -= amount
self.__transactions.append(f"Withdrawal: -${amount}")
JavaScript: Flexible and Modern
class BankAccount {
#transactions = []; // Private field
constructor(owner, balance = 0) {
this.owner = owner;
this._balance = balance;
}
get balance() {
return this._balance;
}
deposit(amount) {
if (amount > 0) {
this._balance += amount;
this.#transactions.push(`Deposit: +$${amount}`);
}
}
withdraw(amount) {
if (amount > 0 && amount <= this._balance) {
this._balance -= amount;
this.#transactions.push(`Withdrawal: -$${amount}`);
}
}
}
When NOT to Use OOP
Sometimes procedural or functional programming is better:
# Simple data transformation - functional is cleaner
prices = [10.99, 23.50, 45.00, 12.75]
discounted = [price * 0.9 for price in prices]
# Don't create a class for this!
class PriceDiscounter:
def __init__(self, discount_rate):
self.discount_rate = discount_rate
def apply_discount(self, prices):
return [price * (1 - self.discount_rate) for price in prices]
Final Thoughts: Objects Changed My Career
That 800-line monster function? After learning OOP, I refactored it into 12 clean, focused classes. Each one had a single responsibility, and the code became:
- Testable: I could test each piece independently
- Maintainable: Bugs were easy to locate and fix
- Reusable: Payment processing logic was used in 5 other projects
- Understandable: New team members could read and modify it
OOP isn't about making simple things complex. It's about making complex things manageable. Whether you're building a coffee shop management system or processing millions of financial transactions, objects help you organize complexity.
Start small. Take a procedural function and extract a class. Define clear responsibilities. Think about what your objects represent in the real world. Before you know it, you'll be designing elegant systems instead of wrestling with spaghetti code.
Remember: every object should have a clear purpose, like every item in your perfectly organized coffee station. If you can't explain what an object does in one sentence, it probably needs to be split up.
Writing this from Fremont Coffee Company, where the objects are as well-organized as their espresso shots. Share your OOP transformation stories @maya_codes_pnw - I love hearing about those "aha!" moments when the concepts click! 🏗️☕
Add Comment
No comments yet. Be the first to comment!