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.
Comments from the original post because I thought they were cool. (You can’t comment here right now because I haven’t set anything else up.)
Steve Jones - Mar 2, 2017
I like this and would like to see more thoughts as you go through here. If you feel like it. As I play more with C# and Python, I find that I appreciate OOP a little more and less. It’s less cumbersome than C++, which I’m not sure I’d want to go back to. But I find myself less concerned about being strict OOP, which has always felt more academic than practical. I sent this to my kid. While considering engineering, he’s started with a CS class using Python and basic programming and he wants to tackle C++ before starting college in the fall. I’m also curious, do you find that when you go top down, from the OOP side, that you end up focusing more on singleton pieces of data and ignore the set based side of your info? Do other classmates? I think that is one of the inherent issues between app devs and database people. We tend to overthink everything in sets and app devs underthink the nature of singletons v larger sets.
Jeremiah Peschka - Mar 3, 2017
As far as languages go, I have mixed feelings about C++. Once you get into modern C++ (11/14/17), things get to feeling a lot like they do in C#, Python, or Java. The downside is that you still have to deal with the majority of C++ code and the availability of all the legacy footguns that C++ still has. If your son needs a C++ book recommendations, I highly recommend Stephen Prata’s C++ Primer Plus. Like you, I’m not particularly concerned about strict OOP, but I can see where it helps. Avoiding things like global mutable state is helpful for a specific class of programs, but I’m not writing those right now. As far as OOP design goes - when I approach design from the top down, I tend to produce more object oriented designs. So rather than have a person with a linked list of addresses, I might build a person with an Addresses collection object and that collection abstracts away finding, changing addresses, etc. For me, and for the few classmates I’ve spoken to about it, top down results in object models where you don’t directly expose data types like a list or set, but you inherit from them to implement your own containers. I a lot of developers thinking in terms of singleton objects comes from the tools that they use to interact with the database. Things like the Active Record tend to enforce a singleton methodology. While these tools provide a good interface for line of business applications that deal with single customer records, they don’t perform well for more complex data manipulation tasks. I also think this is driven by tool focused development - we pick a data access layer before we’ve modeled the application domain and determined the ways that we plan on interacting with the data. Better tooling won’t fix it, though, we just need to do a bit more design up front. For database people, it’s pretty natural to think about things in sets since that’s our bread and butter. Our tools don’t make it terribly easy to think about a singleton object’s data graph (a customer might have an order history, multiple purchasing methods, etc).
Anthony Nocentino - Apr 2, 2017
This is the transition from developer to architect (or whatever you want to call it). No longer are you just slinging code to an end, but leveraging your toolset to solve a complicated problem. It’s great to get a little insight into your academic journey. All the best!
Jeremiah Peschka - Apr 2, 2017
Thanks, man. It feels more like the journey from practitioner to academic, or at least educated practitioner. My goal is to keep posting things as I write them for classes, as long as it’s general enough to post on the internets. Hooray for being forced to write regularly!