How to write more idiomatic constructors in Kotlin
Introduction
Kotlin is a modern object-oriented language which has been gaining traction year over year. While it’s earned a lot of fans in the Android world, it has also been successfully used in many applications from the frontend to the backend.
Below, I will make the case that replacing constructors for static factory methods can make your code more flexible and idiomatic, resulting in a more consistent, clean and readable API.
In this article, I will focus on constructors and extension methods which are features present in Kotlin as well as in many other languages so the ideas should be applicable there as well.
Consider a simple class representing a user in the system.
The problem
Classes can have one primary constructor and as many secondary constructors as we need to allow consumers to create instances with different arguments.
Having many constructors in one class might seem okay but when writing APIs it is important to consider readability and predictability. In other words, our interfaces should be self-explanatory to consumers of the API.
Constructors have two sneaky properties:
- Unlike regular functions, they have no name.
- They can only return the instance of the class they belong to.
Continuing with the User
class example, the application has grown and we need to instantiate users from JSON and another type of user. One could add secondary constructors to our class:
This solution would work but it has two issues:
- It is not clear from the API that a constructor with one
String
argument expects a JSON string in a set format. - We have added unnecessary coupling with
FacebookUser
, an external entity, directly in our own model.
While this is a very simple example it’s easy to see how it can get increasingly out of hand pretty quickly.
The solution
Some inspiration
There is nothing wrong with borrowing ideas from other languages, right? Dart, a cool language open-sourced by Google, solves the aforementioned issues elegantly through the factory
keyword.
What’s interesting about factory methods in Dart is that they can be named and, since they are regular methods, can return any implementation of the class in question. This allows the developer to create specialized implementations of an interface and hide them from the consumer.
But this is a Kotlin article, right?
Of course, we can achieve this in Kotlin through static functions. As a matter of fact, libraries do this all the time. ie: listOf
, Mono.fromCallable
, etc.
Now we can fix the User
class using companion methods:
Calling User.fromJson(string)
or User.fromFacebookUser(facebookUser)
is much clearer than User(string)
or User(facebookUser)
. However, the coupling with FacebookUser
is still there. Kotlin’s extension methods are of great help with that.
The Facebook logic can now be moved elsewhere without compromising the idiomatic API shown earlier. A good place would be in a dedicated Facebook package where other Facebook-specific logic lives.
This approach provides the flexibility to create new constructors as the application needs evolve. It also allows hiding away the instantiation logic from the consumer so the returned object can, for example, come from a cache or a singleton where the consumer shouldn’t be aware of that fact but it is acceptable to share the instance between calls.
Final remarks
We have learned how to create more idiomatic constructors by not using constructors with Kotlin or any language with similar features which provides some benefits:
- Idiomatic and meaningful constructors.
- Freedom to return specialized subclasses of an interface.
- Decoupling intent from the instantiation logic.
It is important to note that this in no way aids with creating complex objects which several dependencies. In such a case, a regular factory class with the appropriate DI would be a much better approach.