In a recent code review one of my colleagues was concerned that part of the code I wrote might cause us problems because of the null object pattern I chose to use.
The code was something like this:
and my colleague was referring to the Nobody
usage.
That got me thinking. Can this usage of sealed classes be considered as an implementation of the null object pattern? Also, why is the usage of this design pattern a bad thing?
Null object pattern (wikipedia)
So, what is this pattern? This pattern is one way to solve the
we don’t want a method to return a null value and force ourselves to check it before usage
(my definition :P)
Ok, it might be better to have an example:
Lets say that we have a service that returns the workout we did in a particular date. Each workout has the duration of the exercise and if we did not workout on the given date the service returns null (we’ll pretend for a moment that Kotlin does not have null safety or those great extensions like map, sum etc):
By introducing a null object
, for those days that we did not workout, we can remove the null checks and have a more readable code:
Why is this a bad design?
From the example we could easily conclude that this pattern is quite helpful. Right?
Well, as everything in our industry: it depends! When we need a way to have some default values so that our calculations won’t crash and burn then it is a good choice. But if we use it in ways that we hide things we might end up in false successes.
For example, lets assume that we have a storage interface and a factory function that returns the proper storage implementation depending on a given value:
If something is not configured well we might end up using, through out our entire project, the NullStorage
thinking that we have saved everything when in reality we lost our data!
In case you missed it there is a space in the cloud-type so no matter how many times we ask for the “cloud”-storage we will get the null-storage back. Silly example with a silly mistake but imagine what could go wrong in real projects!
So, back to the code that started all this
First thing is first:
Q: Is this usage of sealed classes an implementation of the null object pattern?
A: No. The Nobody
object is just a representation of a valid state for the AssignedTo
concept and does not provide any default values or hides any method usage.
Q: Can sealed classes be used for implementing the null object pattern?
A: Yes. Sealed classes can help us in having many values for one concept but the fact that we can use full fledged classes, that can also inherit a bunch of things, is quite powerful and can be easily misused. I don’t see anything stopping us from implementing the storage-example using sealed classes so we just need to think things carefully.
And now the twist:
As is, the code does not implement the design pattern but with a small change we can make it not only to implement it but to do it badly too! We’ll just move the name property to the parent and have Nobody
provide an empty value:
Q: Why is this bad? It does not hide anything, it provides a default value and to be honest we just need to pass “nobody”, instead of an empty string, to the super constructor.
A: Well, this time it depends on where we use the pattern and not how. If this code is part of our core layer then we are allowing this layer to decide on presentation issues! It should be the presentation layer that will check what value the assignedTo
has and print “nobody”!
Our previous implementation of AssignedTo
was forcing us to make the distinction between all of its values so we did not have much of a choice but to let the print
function decide.
In conclusion
Think twice before using the null object pattern and, if you are in a team, try to put code reviews as part of your work flow. It is always good to have a fresh pair of eyes look at your code and even better to have a few people to discuss (and argue) about your choices.
Trying to explain something will make you understand it better!
Top comments (0)