Null Object Pattern in Swift
When something returns nil, it spreads like a disease. Checks for nil and optional unwrapping pollute all the functions around. The whole object-oriented approach suffers. Instead of creating objects and telling them to do work, we write procedural code “if this is not nil, do that, else that.”
Swift optionals formalized working with null references. It’s definitely a step forward compared to Objective-C, but we could take it even further.
One solution to this problem would be to throw an error. A function either returns a non-optional or throws. It’s a perfectly normal solution, but sometimes one has to indicate the absence of an object.
Enter the Null Object pattern. Null Objects eliminate optionals and encapsulate special cases making code cleaner.
For example, if a phone call registry returns an optional phone call, we have to unwrap it before telling it to hang up.
But if it returned a non-optional phone call, the code would become shorter.
One optional unwrapping doesn’t look like a big deal first, but it tends to spread all over the code base. It is also very likely that there will be places with multiple optional unwrapping, also known as Pyramid of Doom.
Swift guard statements help to some degree, but don’t make the function shorter or significantly more readable.
Compare to an example with null objects.
The Law of Demeter tells us not to call any methods on the phone call we receive from the account object. The account object should actually have a method like hangUpFirstCall(). But now the story is only about optionals and nil.
Here are the example implementations of the Null Object pattern in Swift. One approach uses a protocol, another one—an inheritance. Both of them provide a way to manually check if the object is null. Although the whole reason for using the Null Object pattern is to avoid those checks and hide the special case handling inside a null object, sometimes it is still needed to do a check from the outside. The number of those checks, however, is expected to be much smaller.
Note that the NullPhoneCall class is private, it should be defined in the same file as the PhoneCall class. It shouldn’t be possible to create an instance of NullPhoneCall from the outside. The only way to access it for the checks is by accessing PhoneCall.nullPhoneCall type property.
Swift allows us to create APIs with a clear indication if something can be null. However, when an object received from a function can be null, the additional checks or unwrapping is needed. Those checks make the functions bigger, tend to spread out, and often are just a duplicate code. In addition to creating throwing functions that return non-optional types, the Null Object pattern can be used to make the code cleaner and easier to understand.