~ read.

Using Null Object Pattern in C#

In this post we are going to talk about what is Null Object Pattern and why do we need it and how are we supposed to integrate it into our programming.

What's Null Object Pattern?

There are shit tones of explanation regarding what is Null Object Pattern, which is why it makes it so confusing. Here let's look at an example to see what it looks like without using Null Object Pattern.

Let's look at the following demo application that is not using Null Object Pattern at the moment.

using System;

namespace Application  
{
    public class Application: IApplication
    {
        private bool loggedInUsername;

        public Application()
        {
            this.loggedInUsername = false;
        }

        public Receipt Purchase(string itemName, decimal Price)
        {

            if (!this.IsUserLoggedIn)
                return null;

            return new Receipt(itemName, Price);
        }
    }
}

(Application.cs)

namespace Application  
{
    public interface IApplication
    {
        Receipt Purchase(string itemName);
    }
}

(IApplication.cs)

namespace Application  
{
    public class Receipt
    {
        public string ItemName { get; private set; }
        public decimal Price { get; private set; }

        public Receipt(string itemName, decimal price)
        {
            this.ItemName = itemName;
            this.Price = price;
        }
    }
}

(Receipt.cs)

Here we have a concrete class called Application which extends from an interface called IApplication which has a method called Purchase() that returns an concrete Receipt class.

It looks fine at the beginning all right?

Have you ever found yourself checking whether null exists all the time? I bet like shit tones of time right? Of cause we need to check whether user is login in order to allow them to purchase.

But each time we check null it will somehow make the code more complex since it introduces another condition, and also it doesn't explain the reason of returning null.

It is too much of me saying at the moment, let's introduce Null Object Pattern here.

namespace Application  
{
    public interface IPurchaseReport
    {
        string result();
    }
}

(IPurchaseReport.cs)

namespace Application  
{
    public class Receipt : IPurchaseReport
    {
        public string ItemName { get; private set; }
        public decimal Price { get; private set; }

        public Receipt(string itemName, decimal price)
        {
            this.ItemName = itemName;
            this.Price = price;
        }

        public string result()
        {
            return string.Format("Thank you for buying {0} for {1:C}.",
                                 this.ItemName, this.price);
        }
    }
}

(Receipt.cs)

namespace Application  
{
    internal class FailedPurchase: IPurchaseReport
    {
        public string result()
        {
            return "Purchase failed.";
        }
    }
}

(FailedPurchase.cs)

namespace Application  
{
    public interface IApplication
    {
        IPurchaseReport Purchase(string itemName);
    }
}

(IApplication.cs)

using System;

namespace Application  
{
    public class Application: IApplication
    {
        private bool loggedInUsername;

        public Application()
        {
            this.loggedInUsername = false;
        }

        public IPurchaseReport Purchase(string itemName, decimal Price)
        {

            if (!this.IsUserLoggedIn)
                return FailedPurchase.result();

            return new Receipt(itemName, Price);
        }
    }
}

(Application.cs)

Here as we can see here we bring in a new Interface called IPurchaseReport.

Why? Cause previously Purchase method from IPurchaseReport return Receipt object as the result which is a concrete class that is really hard for us to return an error.

So we introduce an abstraction layer between them then create a new concrete class called FailedPurchase that extends from IPurchaseReport Interface. So if there is any false purchase we can return FailedPurchase concrete class instead of returning null that indicating no reason of failures.

End

This solution could be more effective if we make FailedPurchase as a singleton as well. Thanks for reading this post and if you have any opinions or ideas please leave it in the comment below. Thanks.

comments powered by Disqus