Object Oriented Programming

During two semesters of object-oriented programming at NTNU, I spent more time learning how to organize a program than learning the syntax. I was used to describing a sequence of steps. Designing objects meant deciding where state belonged and which part of the program should be responsible for changing it.
A different mental model
A task manager was a useful example because a task has both state and operations that change that state:
from dataclasses import dataclass |
Putting completion state and the mark_complete operation
in the same class gives the rest of the program one place to work with a
task. It also gives the class a clear responsibility, which makes later
changes easier to reason about.
Inheritance
An animal example is a convenient way to show inheritance and polymorphism. Different subclasses can provide their own implementation of a shared operation:
from abc import ABC, abstractmethod |
I found it easy to overuse inheritance at first. A class hierarchy works when the subclasses can sensibly be used as the parent type, but many relationships are better expressed by giving one object another object to use. After building a few tangled hierarchies, I started considering composition earlier.
SOLID
The SOLID principles felt abstract when they first came up in lectures. Refactoring my own code gave me examples of the problems they were trying to address.
Single responsibility was the most useful starting point. In a task manager, managing tasks and delivering notifications are separate concerns, so I moved the notification code into its own service:
from typing import Protocol |
With notifications behind a separate interface, the task manager doesn’t need to know how a message gets delivered. An email or Slack implementation can use the same interface without changing the task-management logic.
Design patterns
I initially learned design patterns as names and diagrams from a textbook. They were easier to understand after I’d written similar code and run into the problems myself.
For example, the observer pattern lets several notification handlers subscribe to changes in a task:
from abc import ABC, abstractmethod |
The subject keeps track of its subscribers and notifies them when something happens. A new subscriber can be added without teaching the subject how that subscriber handles the event.
Refactoring larger programs
I would spend more time thinking about how objects depend on one another. Making each class tidy doesn’t help much if every change requires updates across half the program.
Refactoring was useful because I could compare two designs for a problem I already understood. The principles were much easier to apply when I had a specific source of confusion or duplication to fix.
A lot of this came together in Recess Chess, a chess engine I built with classmates. Representing pieces, moves, board states, and turns gave us enough interacting parts to see where a design helped and where it made the code harder to change.