Object Oriented Programming

I spent two semesters learning object oriented programming and honestly, it was rough. The syntax wasn’t even the hard part. What really messed with my head was having to think about programs in a completely different way. You go from writing step-by-step instructions to thinking about objects that interact with each other.

How I Had to Rewire My Brain

With procedural programming, I’d think “first do this, then do that, then this other thing.” OOP turned that upside down. Suddenly I’m thinking “what things exist in my program, and how do they talk to each other?”

Here’s an example that really helped it click for me:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from dataclasses import dataclass
from datetime import datetime

@dataclass
class Task:
title: str
created_at: datetime
completed_at: datetime | None = None
subtasks: list['Task'] | None = None

def mark_complete(self) -> None:
self.completed_at = datetime.now()
if self.subtasks:
for subtask in self.subtasks:
subtask.mark_complete()

@property
def is_complete(self) -> bool:
if not self.completed_at:
return False
if self.subtasks:
return all(subtask.is_complete for subtask in self.subtasks)
return True

Instead of asking “How do I track task completion?” I started asking “What does it mean to be a Task, and what should Tasks be able to do?”

When Inheritance Finally Clicked

There’s this classic example that made it all make sense:

This shows how you can model real world relationships in code. Here’s how it works:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from abc import ABC, abstractmethod

class Animal(ABC):
def __init__(self, name: str, age: int):
self.name = name
self.age = age

def make_sound(self) -> str:
return "..."

@abstractmethod
def move(self) -> None:
pass

class Dog(Animal):
def __init__(self, name: str, age: int, breed: str):
super().__init__(name, age)
self.breed = breed

def fetch(self, item: str) -> None:
print(f"{self.name} fetched the {item}!")

def move(self) -> None:
print(f"{self.name} runs on four legs.")

SOLID Principles Aren’t Just Theory

We kept hearing about these SOLID principles. At first they sounded like academic BS. But turns out they do help. Take Single Responsibility Principle - basically each class should do one thing.

I used this to clean up my task manager:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from typing import Protocol
from task import Task

class NotificationService(Protocol):
def notify(self, message: str) -> None:
pass

class TaskManager:
def __init__(self, notification_service: NotificationService) -> None:
self.notification_service = notification_service
self.tasks: list[Task] = []

def add_task(self, task: Task) -> None:
self.tasks.append(task)
self.notification_service.notify(f"New task created: {task.title}")

def complete_task(self, task_title: str) -> None:
task = self._find_task(task_title)
if task:
task.mark_complete()
self.notification_service.notify(f"Task Completed: {task.title}")

def _find_task(self, title: str) -> Task | None:
return next((task for task in self.tasks if task.title == title), None)

Design Patterns Just… Happen

Something that surprised me: design patterns aren’t just textbook stuff. They solve real problems. Like the Observer pattern - perfect for notifications:

Here’s mine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from abc import ABC, abstractmethod

class Observer(ABC):
@abstractmethod
def update(self, event: str) -> None:
pass

class Subject(ABC):
def __init__(self):
self._observers: set[Observer] = set()

def attach(self, observer: Observer) -> None:
self._observers.add(observer)

def detach(self, observer: Observer) -> None:
self._observers.discard(observer)

def notify(self, event: str) -> None:
for observer in self._observers:
observer.update(event)

class TaskSubject(Subject):
def create_task(self, title: str) -> None:
# Task creation logic here
self.notify(f"Task created: {title}")

The Main Takeaways

Just because you can use inheritance doesn’t mean you should. Sometimes it’s cleaner to have objects contain other objects instead of inheriting from them.

And honestly, the interactions between objects matter way more than the objects themselves. The power isn’t in modeling individual things - it’s how those things work together.

Design patterns aren’t something you memorize. When you hit a problem, the pattern that solves it just makes sense.

The biggest thing though: good OOP takes practice. You gotta write some terrible code first to understand why all these principles exist.

I used a lot of this stuff when building Recess Chess with some classmates. Modeling chess pieces, game states, and player moves really showed me how useful OOP is for complicated systems.

If you’re learning this and it feels like too much, keep going. After you build a couple projects, it’ll click.