My journey of demonstrating how S.O.L.I.D. design principles can be applied in a range of programming languages going back 50+ years gets bang up-to-date with an example in Kotlin.
Now, you could probably argue that Kotlin is a no-brainer where this is concerned. Anything I can do in Java I can do in Kotlin, if I choose to. Kotlin has classes, interfaces and constructors. We can make data private just as easily as in Java. But still, I hear objections from developers doing pure FP in Kotlin that either:
a. “OO” design principles don’t apply (which is why I’ve stopped calling them that – they’re modular design principles), or…
b. We don’t need to apply S.O.L.I.D. to functional programming, because FP is innately S.O.L.I.D. (Spoiler Alert: it isn’t.)
Whereas with older languages like C and Fortran 77, I’m working harder to get around some language limitations, with languages like Kotlin and Clojure, I’m having to work harder to get around cultural limitations. To be fair, this is not a new phenomenon. I can clearly recall programmers telling me – in the heydey of OOP in the mid-to-late 90s – that you didn’t need to think about things like modularity because OOP is innately modular. (Spoiler Alert: it wasn’t.) Give a C programmer C++ and they’ll write you procedural C++ code. And, as my previous post illustrated, give a C++ programmer C and they’ll find a way to create objects with it.
I define code that’s effectively modular by three key properties:
- It’s made of discrete parts that do one job each
- Those parts know as little as possible about each other
- Those parts are easily swappable
There’s no programming language on Earth that forces us to write code that ticks all three boxes. You have to tick the boxes yourself by the design choices you make.
Granted, there are things we need from a programming language to enabe effective modularity:
- The ability to break code up into discrete reusable units (i.e., modules)
- The ability to control what client code can see of a module (or – in the case of Ruby, Python, JS etc – the ability to make that not matter with dynamic binding)
- The ability to dynamically substitute a different implementation without re-writing the client code
These days, the vast majority of programming languages available to us score 3/3. There are some older languages that offer no mechanism for polymorphism, but you’re very probably not using one of them on a regular basis. You can even do it to a limited extent in Fortran 77. Any language that allows us to pass a function or procedure pointer/reference as a parameter is technically polymorphic.
Anyhoo, here we are in 2019, and the shiny new kid on the code block is JetBrain’s Kotlin. It’s a derivative of Java, with spiffy FP sensibilities. To an old Java hand like me, it takes no time at all to learn. Here’s the carpet quote example in Kotlin.
Again, the first problem that leaps out at me is that this function is doing more than one thing. It breaks the Single Responsibility principle. Let’s refactor each reason to change into its own function in its own module.
The next thing that’s bothering me is that our data classes Room and Carpet are unencapsulated. That’s always bugs me. In OO design, we say that data classes are a code smell. They hurt us in FP, too. A dependency’s a dependency. Let’s refactor our area() and price() functions into closures that hide the data from quote().
And yes, I would just as readily use a class instead of a closure. I’m not an FP purist.
This refactoring has killed two birds with one stone: we’re hiding the data from quote() and now we can easily swap in a different implementation for calculating room area and carpet price without changing quote().
For example, what about a circular room?
That ticks the O, the L and the D in S.O.L.I.D. – our dependencies are now easily swappable. So, so far, we’ve covered S.O.L.D. as well as Tell, Don’t Ask.
What about Interface Segregation? Well, unlike many languages that support FP, Kotlin also has direct support for classes that implement multiple client-specific interfaces. If we can do it in Java, we can do it in Kotlin.
You can view the source files at https://github.com/jasongorman/kotlin_solid