Object Oriented Programming Matters
And Here's Why...
Introduction
Right now, functional programming is all the rage. Languages like Haskell and JavaScript are hip. While JavaScript can be mostly object-oriented through languages like TypeScript, doing objects in JavaScript is becoming less common because of the rise of functional programming.
Proponents of functional programming tout things like pure functions making testing easier and immutability reducing side-effects and more. This is all fine and well, but it ignores the biggest reason to use Object Oriented. The world is made of objects, not linear functions (well, not exactly, but a linear function that can accept all the parameters of the world would be completely unmaintainable).
Using Object Oriented Programming (OOP) is essentially creating a system that defines what is to be done (Interfaces/Abstract Classes) and how it is done (Concrete Classes). OOP breaks down a system into contracts that define interaction, and implementations that perform the actions. Objects in both OOP and the Real World interact with each other through a definable set of conditions based on Use Cases and normal behaviors, and returning exceptional behaviors as Exceptions. Pure Functions cannot express ambiguity since ambiguity is a side-effect.
A Simple System
For over 100 years, we have had telephones. Although technology has changed dramatically, the use cases of the telephone have remained constant.
100 Years of Telephone Use Cases
Callers dial a number, the receiver answers the call, they talk, and one of the parties hangs up.
Such a fundamentally simple system!
Though the technology has drastically evolved, a modern smart phone can call a wall mounted operator assisted phone today, and vice versa.
So why does this work? Because the world is composed of objects that implement interfaces (inheritance) with a specific functionality (encapsulation). Some devices evolve with minor changes, such as a pulse dial phone evolving into a touch tone phone (polymorphism).
Telephones are just end points in this system. Interconnecting telephones are Telephone Networks that accept call requests, figuring out how to route the call to the recipient, and then notify the recipient of an incoming call. We have three Interfaces that define the interaction between the four simple use cases.
Three Interfaces that Changed the World
Not only is this critical system defined by three Interfaces, but the Interfaces themselves are extremely simple. If you’ve ever heard the term “Keep It Simple, Stupid,” then you know this is the epitome of that saying. The system has stayed essentially the same for 100+ years. All we have to do is fill in the blanks!
The most fundamental part of the system is the ITelephone
. This is what a caller or receiver uses to talk, which is the fundamental use case of the entire purpose of the entire system.
The telephone network started with a caller picking up a speaker, clicking a lever a few times, and working with operators who would route your call from network to network until the receiver’s phone rang. It was crude, but effective.
Today, a caller picks up a speaker, touches some numbers, computers figure out the most efficient route to the receiver and the receiver’s phone rings.
In the last 100 years, ITelephone
, ITelephoneNetwork
and IRoute
have all evolved as technology has changed, but that doesn’t matter.
ITelephone
has gone from a speaker and clicker to a sophisticated hand-held computer that just happens to also participate in phone calls. Backwards compatibility is a hallmark of this evolution. The ITelephone Interface
enforces this common set of available actions and receivers so that any instance of ITelephone
can talk to any instance of ITelephoneNetwork
through adapters that reside in between devices that handle the translation between different technologies.
ITelephoneNetwork
defines the basic communication between each network along the path of a call. Again, adapters fill in the technological gaps, but they simply translate one technology to another.
We can look at the interaction of these Use Cases through a (very) simple activity diagram.
Can it really be this simple?
Can three Interfaces really describe all of this? Yes, yes they can. Of course, the nitty-gritty details of each action aren’t reflected here, but they aren’t relevant either. Please note, at this point, we haven’t created a single class. We have defined the Use Cases, the Interfaces, and the Activities that happen during a phone call. At this point, you may be tempted to dive right in and start creating those implementations, but first let's define how those interfaces interact with each other with a sequence diagram.
Lots of work gets defined here, and the implementation still doesn’t matter.
The sequence diagram is read from left-to-right, top-to-bottom. Each line represents a method, event, or return value along the way. Everything here is in the Interfaces and everything in the Interfaces is here as well. The Sequence matches the Activities which fulfill the Use Cases. For 100 years, a Hodge-podge of various technologies has implemented this sequence, each step of the way not caring at all about the implementation of the next
Conclusion
In most books and articles, you would see a concrete example of the system implemented. There would be the RotaryTelephone
, the TouchToneTelephone
, the CellPhone
and the SmartPhone
. There would be Operators plugging in wires to panels and computers routing calls digitally. You would see CopperNetworks
, FiberNetworks
, CellularNetworks
, and DigitalNetworks
. You won’t see those implementations here. Why? Because they don’t matter.
This is the epitome of what OOP is intended to be. Interfaces encapsulate functionality and Objects implement the Interfaces through new or evolutionary designs, but because of the simply designed system, implementations don’t matter.
I hope boiling down OOP to a simple, world-changing system helps with your understanding of the importance of good design and how good design leads to good systems. Implementation can change over time — this is inevitable — and you can simply inherit an existing object or create a completely new object that implements the common interface. OOP allows you to do all of these things in ways that just make sense. There are no pure-function gyrations requiring implementations for each combination of technology to technology. There are no giant libraries of API calls to provide new functionality every time technology advances. You simply add or extend what needs to be added or extended and be done with it. Done correctly, existing systems are not impacted.