Can we discover a new paradigm “FOOP”?
Functional programming(FP) seems to be completely in vogue these days. While I do think FP has many benefits, I often have a hard time with what sometimes seems to be a dogmatic comparison that FP is superior to object-oriented (OO) programming.
Contrary to popular belief, I think that OO and FP are closer together than they might appear. At least this seems to be particularly true if the OO code is written with SOLID design principles in mind.
In this article, we are going to explore refactoring from SOLID object-oriented (OO) code to a more functional programming (FP) style using TypeScript.
In addition to the “how-to” aspect, we’ll look at each refactoring from a testability perspective. I find it is a good gauge of code quality. If its easy to test, there is a high probability that there is not a bunch of funky state or hidden dependencies.
Without further ado…. let’s refactor!
For this example, we will use a very very simplified bank account example. We’ll have an Account domain object and our use case is opening a new account.
As you can see in this example, this is a pretty typical SOLID code. We have some stateless service class that contains the business rules for our use case, and we hold a dependency on our data layer to be able to persist our account information. This is easily testable since we can inject a fake implementation using an in-memory database or mock.
In our first refactoring to FP, we need to actually make this a function. And as they say, “a closure is a poor man’s object”. So let’s turn this into a functional closure.
Are we functional yet? Not quite. We could still potentially keep a private state in this iteration, so let’s remove the closure and bring in a higher-order function.
Hey, this is pretty cool! We are passing in the dependency directly to the function. We factored out ability to keep state in the closure and it’s testable all the same. It feels like an interface with one method and a built-in constructor. I dig it.
Still, there is work to do. Can we factor out the dependency altogether? First, we can take the creation of the account object and extract it to its own function.
Notice that the createAccount function is now pure. And instead of depending on the interface, we can just write our saveAccount function implementation directly.
Lastly, we can compose the two to satisfy our use case.
But wait, how is this testable!? We are unable to inject our fake dao into the function. The answer here is that we do not unit test the composition. Instead we unit test the pure parts which are very straightforward.
In order to test the entire composition, we would need an integration test (a true testament to the name).
In the end, maybe the goal is not the decision of OO or FP, but more so of stateless programming with clear responsibilities and limited coupling.
Like most things in life, it’s not all black and white. Notice that all these refactorings were viable from the start. Each is stateless, testable, and has clear responsibilities! The main difference here is dependency management by using dependency inversion or dependency rejection.
I think I’d like to conclude that maybe the balance lies somewhere in the middle. Personally, I have a preference for the higher-order function refactoring. It seems to have the best of both worlds in that it:
- Avoids the spaghetti that can come along with classes and closures
- Doesn’t make things so fine-grained that it’s hard to keep track of (functional composition)
Maybe we can invent a new paradigm called FOOP? Thanks for reading!
There and Back Again: Refactoring Object-Oriented to Functional Programming was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.