N.B. This was originally written as a term paper for Portland State University’s CS202 – Programming Systems course.
I’ve been working with object oriented programming (OOP) and software development for longer than I’d care to admit. I’ve found OOP to be cumbersome and prone to odd behavior. Shared mutable state has caused me a lot of problems in the past, and over the years I grew to distrust OOP. It was only until taking a more considered and thoughtful look at OOP in CS202 that I started to appreciate OOP.
The First Assignment
I favor data driven approaches to problem solving. Coming out of seven years of database work, it makes sense. All problems are data problems in databases. My approach to the first programming assignment, building a simulation of a mass transit system, was a hybrid of an OOP and data driven approach. Initially, I created a lot of wrapper functions and wrapper objects around pure data structures. Unfortunately, I didn’t notice this until I was most of the way through the assignment. Frankly, when you’re almost done with something and a deadline approaches, you ship the code and hope for the best. Also, the changes would have require a huge amount of rewriting which would have added a lot of bugs to the code. Instead, I did what I could to work around the design.
After I finished the assignment, I set aside time and I worked through thought experiments to figure out how I could have made the application more object oriented. The improvements to the design focused around moving functionality into classes. For example, in the program that I submitted, my application had a separate streetcar line class and list class. In my design review, I realized that I could have created streetcar lines as a subclass of the list.
Although the approach above makes sense from a data structures perspective, this doesn’t truly reflect OOP ideas. Instead, the streetcar line should be a list of streetcars. To accomplish this, the line class should have contained the list functionality. Although this would complicate the line class, it would alleviate the need for many wrapper functions that call into the list class to get work done.
Program 2: OOP Boogaloo
Our second programming assignment focused on tracking a history of mass transit rides. We use a lot of public transit here in Portland. I won’t lie, my first design attempt at this program was very data driven. This worked in my favor – once I identified the core data structures of the application, I was able to take a step back and ask “How can I make this more object oriented?”
Usually programs come out as data driven when you take a bottom up approach to things – data structures come first, then code. Now that I understood the data structures in the program, I set them aside and redesigned the program from the top down. Designing from the top down made it easier to use OOP. I focused on designing a system around task responsibility and management. Each class had a clear responsibility. If a class had no clear responsibility, I questioned the purpose of the class. Unfortunately, one pure wrapper class remained in the program. I created a `RiderHistory` class that ended up containing nothing but the `Popularity` metrics for the program. At one point, this class was intended to provide more comprehensive functionality but after re-reading the assignment, I realized that it wasn’t necessary. Through sheer laziness I didn’t remove the wrapper class and so it remains like so many other bad decisions.
Focusing on task management instead of data management made it easier to produce a more ergonomic and intuitive design. The end result was much better, with the exception of that `RiderHistory` class.
Third Time’s the Charm, Right?
The third programming assignment focused on building a contact management system. This would have been pretty simple, but there was an added twist – we had the option of implementing the contact management system using a self-balancing tree.
Because the tree featured so prominently in the project, I took a more data driven approach on this assignment. That’s not to say the code wasn’t object oriented, but the software in question focused strongly on the interactions between data types. It’s hard to avoid when you’re building complex data structures like a tree.
As I understand it, there’s a design philosophy where a `ContactManagementSystem` would inherit from a `Tree` and then a `Person` might inherit from a `TreeNode`, but this approach mixes data structures with application structures. To clarify – data structures exist to store data for the application, while the application structures exist to act. I find that the approach I’ve been taking produces code that’s easier to reason about, implement, and fix.
The most enjoyable aspect of the assignment was implementing a self-balancing tree. (For the record, I implemented a Red-Black tree.) I made several attempts to implement the tree, but I was only successful when I stepped back and described the responsibilities of each part of the tree, as I designed it. My `TreeNode` class ended up being a dumb object that only existed to hold and move data and the rest of the functionality, including balancing, was the responsibility of the `Tree`.
In a perfect world, I would have preferred to make the `TreeNode` a private struct or class inside of the `Tree` class. While this design may not be purely OOP, it does remove the `TreeNode` as something that end users can even interact with and makes it a private implementation detail of the `Tree`.
A Side Note About Java
Programs 4 and 5 – an AirBNB clone that I wittily called GroundBNB – were implemented in Java. While writing code for programs 4 and 5, I noticed that my classes felt more like built-in Java classes than anything I wrote using C++. By looking at the Java standard library, I could see how methods were named in the standard library classes and use similar names. In addition, because of this (or the lack of operator overloading) it was definitely easier to create classes that behaved in ways that feel intuitive. Having that intuition about class and method names also made it significantly easier to reason about the code. I could put down code for several days, pick it back up, and immediately understand where I left off. With C++, I found it took time to understand what I had been doing when I left off.
The Far Away Lands of Java
Programs 4 and 5 were implemented in Java. I think I mentioned that already. Whatever, that was a paragraph ago and it was probably garbage collected.
In these last two assignments, I focused on writing code that accomplished specific tasks. This approach was very helpful in creating compact and reusable code. Barring the `TreeNode` class, every class had a specific and concrete purpose. And by focusing on behavior, I minimized the number of getters and setters. This approach created code that was easier to reason about than previous approaches and the entire application felt much smaller than other applications.
Working with Java provided an interesting change of pace. With C++, there was always a feeling that I was fiddling with bits and moving data instruction by instruction. Even after building abstractions on top of my data, I still had to be aware of this behavior under the covers. C++ feels like it requires a significant level of understanding about the implementation of the software being used. Maybe this comes from a lack of experience, or from the way I wrote my code, but when working with C++, I felt like I couldn’t escape the implementation details.
Over this term, I’ve found that my opinion of OOP has shifted. By focusing on creating classes with focused responsibility, I gained a deeper appreciation for OOP. I also learned how to use OOP to design solutions and solve problems. Shared mutable state doesn’t have to be a problem with object-oriented design. By building software with concrete responsibilities it’s easier to avoid problematic patterns like shared mutable state, getters and setters, and classes that only exist to transfer data.