From OOP Interfaces to Functional Modules: A Journey to Simplicity and Composability
Introduction
If you've spent years in object-oriented programming, you're used to defining interfaces and switching implementations through subclassing or dependency injection frameworks. This model works, but when transitioning to a functional programming language like Scala, you might ask:
"Why abandon interfaces? Aren't they good enough?"
In this article, we’ll walk through a common OOP scenario, then gradually morph it into a purely functional approach using Scala 3 — without relying on subtyping. Along the way, we’ll highlight why this shift is not just syntactic, but also deeply empowering.
The Classic OOP Style (Java)
Let’s consider a simple logging service:
The Functional Module Pattern (Scala 3)
We now reimagine the same logic in a purely functional way — no interfaces, no subtyping, just values and functions.
Wait, Isn’t That Just the Same?
Functionally yes — both approaches provide abstraction and pluggability. But there are key differences once we zoom out to real-world systems.
Let’s break it down.
Why Functional Modules Are Better (in FP World)
Recommended by LinkedIn
1. They’re First-Class Values
You can pass them around, transform them, or dynamically create them.
2. No Type Hierarchies
Avoid the diamond problem, abstract classes, and tight coupling. You don’t need sealed, final, or marker traits.
3. Transparent & Composable
Modules can be composed naturally:
4. Simplified Testing
Creating test doubles is trivial:
5. No Runtime Dispatch
All dispatch is structural, not nominal — it’s based on the fields in the record, not class inheritance.
6. Plays Nicely with Effects Systems
Conclusion: Familiar Pattern, Better Foundations
Yes, functional modules and OOP interfaces often look similar on the surface. But underneath, the functional approach provides more flexibility, composability, and reasoning power.
You’re not giving up abstraction — you’re gaining value-level composition, structural clarity, and a world where dependencies are data, not magic.
Make the shift — your code (and future you) will thank you.