13 - [Design patterns series] - The Null Object Pattern

Merry Christmas, fellow computer scientists! I hope you’re having a great one. I am currently way too full to even move, so what’s better to do than ~sit~ lie down and write about some design patterns?

Today’s short post is going to be covering a pattern I completely forgot about, but in the process of rereading it realised how much I love - the Null Object Pattern!

Let’s dive in!

The Null Object Pattern

To better understand the Null Object Pattern, sigh, let’s call it NOP for the remainder of the article. So, to better understand NOP, let’s think about what situations we might find it useful for. Let’s imagine it’s 2005 and we’re working at MySpace and we’re developing the user functionality. Our Project Manager (Because I don’t think product management was a thing back then) tells us that we need to create a user system, where users can set their names, a profile picture, and also we want to implement some form of authorisation for certain actions. Like an administration role. We sit down, put our Winamp on shuffle mode and start coding our user class:

class User {
    constructor(name: string, profilePicture: string, role: string) {
        this.name = name;
        this.profilePicture = profilePicture;
        this.role = role;
    }
    getName(): string {
        return this.name;
    }
    getProfilePicture(): string {
        return this.pofilePicture;
    }
    canUserBanOtherUsers(): boolean {
        return this.role === 'admin';
    }
}

Ok, so now we have our base user class, let’s imagine we have a list of users logged in at MySpace:

const users = [
    new User('andy', 'andy.png', 'user'),
    new User('emil', 'emil.png', 'user'),
    new User('will', 'will.png', 'admin)
]

function getUser(name: string): User | null {
    return users.find(user => user.name === name);
}

function populateMySpacePage(name: string) {
    const user = getUser(name);
    const profileData = {};
    if (user && user.getProfilePicture !== undefined && user.getProfilePicture() !== undefined ) {
        profileData.picture = user.getProfilePicture();
    }
    else {
        profileData.picture = 'defaultAvatar.png';
    }
    if (user && user.canUserBanOtherUsers && user.canUserBanOtherUsers() === true) {
        profileData.canBanUsers = true;
    }
    else {
        profileData.canBanUsers = false;
    }
    return profileData;
}

Now, I really hope that you think there is something wrong with this code. There are so many if checks, I am starting to doubt reality.

Following this approach, we have to do multiple if checks in any method where our class method or property might a) not exist b) exist but return a null value, and in those cases, we have to make sure that we are checking everything, and providing some default values when we get a null value.

But what if our design team comes up with a new cool default profile picture? What if admins are not allowed to ban users anymore, but that power is not transfered to another role, such as moderator or a BanHammerWielder? We’d have to go through every place in our code we are interacting with a user and their properties and update our if checks and assing new default values. This can not be good. Humans are not that amazing when it comes to making sure we’ve checked everything and we’re bound to make a few errors and leave some pesky bugs around.

There must be a way to save ourselves all of that trouble… and there is! The NOP!

The NOP is a design pattern that we use when we want to cover the cases when an object is returned as null, and we assign default values right off the bat. Let’s refactor our MySpace code and see the null object in action.

class User {
    constructor(name: string, profilePicture: string, role: string) {
        this.name = name;
        this.profilePicture = profilePicture;
        this.role = role;
    }
    getName(): string {
        return this.name;
    }
    getProfilePicture(): string {
        return this.pofilePicture;
    }
    canUserBanOtherUsers(): boolean {
        return this.role === 'admin';
    }
}

// we don't touch our base class, we leave it as it is

class NullUser extends User {
    constructor() {
        super();
        this.name = 'Unknown User',
        this.profilePicture = 'defaultAvatar.png',
        this.role = 'non-existing' // or any role we might think about
    }
    canUserBanOtherUsers(): boolean {
        false;
    }
}

Ok, so far we’ve extended our User base class with a NullUser class, and we’re defaulting the values in the constructor. We overried the canUserBanOtherUsers method to always return false, cause you know, we didn’t have the same attention to security in 2005, but it’d be weird to let unknown users ban users, and that’s it!

Let’s refactor the rest of our code to use our cool new pattern:

const users = [
    new User('andy', 'andy.png', 'user'),
    new User('emil', 'emil.png', 'user'),
    new User('will', 'will.png', 'admin)
]

function getUser(name: string): User | NullUser {
    return users.find(user => user.name === name) || new NullUser();
}

function populateMySpacePage(name: string) {
    const user = getUser(name);
    const profileData = {};
    profileData.picture = user.picture;
    profileData.canBanUsers = user.canUserBanOtherUsers();
    return profileData;
}

Oh my god, I love deleting code. You can see how simple our code has become already. And if we need to make any changes to the default user, all we need to do is update our NullUser class.

Conclusion

The Null Object Pattern is one of my favourite ones to use and it makes our code so much simpler and cleaner. Give it a go! Also the new Matrix movie is reall bad.

Until next time!


Written by Emil Mladenov - a slavic software developer who decided to use a blog as a digital rubber duck

I also have a podcast