Usually in Object oriented programming, when working with reference value types, developers normally get high chance of encountering what is called NullReferenceException or NullPointerException.
So what this exception is about?
This exception happens when developer tries to access properties or methods of a reference variable that is not referencing any object. If the reference variable that developer tries to access is not referencing any object that mean its value is null. This usually happens with reference types as opposed to value types.
Nulls and NullReferenceExcpetion or (NullPointerException in some languages) was the cause to so many errors and bugs through the development of any application or product.
So how can we deal with such an exception?
Here comes the benefit of a design pattern called Null-Object Pattern.
What is Null-Object-Pattern?
It is a design pattern that that is using object-orientation to remove and reduce the amount of null references by reducing the need of repetitive null checking.
So before explaining the design pattern itself, letβs address the issue first.
Consider we have the following simple app with the following classes
- Student Class
- StudentFactory Class
- Main Program
Student class will define how each student will look like in terms of properties and methods
public class Student
{
public string Name { get; set; }
public double Grade { get; set; }
public Student(string name, double grade)
{
this.Name = name;
this.Grade = grade;
}
public string GetName()
{
return this.Name;
}
public void ToString()
{
Console.WriteLine(this.Name + " - " + this.Grade);
}
}
So each student will have a name and grade as properties. GetName() method for getting the student's name and ToString() method for printing student's info.
StudentFactory class will have the list of all predefined students. For demo purposes, we donβt care here if the students in a list or coming from the database or in a file. All we care about is that StudentFactory class contains the list of all students.
public class StudentFactory
{
public List<Student> students;
public StudentFactory()
{
students = new List<Student>()
{
new Student("Mohamed", 20),
new Student("Diya", 25),
new Student("Anas", 15),
new Student("Roaa", 12),
new Student("Rawan", 10)
};
}
public Student GetStudent(string name)
{
var student = this.students.FirstOrDefault(n => n.Name == name);
if (student != null)
return student;
return null;
}
}
StudentFactory class also contains method called GetStudent() that will search in the students list based on the provided name from the user and returns null in case the name is not there.
Now the Main Program class, here we define an object of StudentFactory class. After that we tries to get some students using the name of the student and store the results in variables s1, s2, s3 and s4 respectively. After that to print the student information, we have to check if each variable of the above is null or not because what will happen if we provided a name that is not in the students list? The value of the variable will result in null. So if we didnβt check if the variable is null or not, the program will crash resulting in NullReferenceException.
static void Main(string[] args)
{
var studentFactory = new StudentFactory();
var s1 = studentFactory.GetStudent("Mohamed");
var s2 = studentFactory.GetStudent("Mohannad");
var s3 = studentFactory.GetStudent("Yasser");
var s4 = studentFactory.GetStudent("Rawan");
Console.WriteLine("Students List....");
//Old Way
if(s1 != null)
{
s1.ToString();
}
else
{
Console.WriteLine("Student Mohamed is not on the list");
}
if (s2 != null)
{
s2.ToString();
}
else
{
Console.WriteLine("Student Mohannad is not on the list");
}
if (s3 != null)
{
s3.ToString();
}
else
{
Console.WriteLine("Student Yasser is not on the list");
}
if (s4 != null)
{
s4.ToString();
}
else
{
Console.WriteLine("Student Rawan is not on the list");
}
}
Now you can see the resulting code contains a lot of repetitive null-checking which result in a code that is not readable or maintainable. Now comes the benefit of Null-Object Pattern. So what is the idea behind it?
- We start off by having a common abstraction (like an interface of a base class)
- Then we have the client code that makes use of this common abstraction
- Then we may have one or two real implementation of this common abstraction (implementing the interface or inheriting from the base class)
- Additionaly, we have to create null object implementation of this common abstraction
- This will contain the "Do-nothing behavior", when the client code executes the code within the null object implementation.
- Important --> The client code will treat both the null object / real objects the same way
So first, for refactoring the code, let's create an interface that will act as the common abstraction and call it IStudent
public interface IStudent
{
string GetName();
void ToString();
}
This interface will contains methods that will be shared in both real objects and Null objects.
After that make some small changes to Student class to make it implement IStudent interface like this
public class Student : IStudent
{
public string Name { get; set; }
public double Grade { get; set; }
public Student(string name, double grade)
{
this.Name = name;
this.Grade = grade;
}
public string GetName()
{
return this.Name;
}
void IStudent.ToString()
{
Console.WriteLine(this.Name + " - " + this.Grade);
}
}
Now comes the interesting part and the real benefit of object null pattern, we must create null object implementation that will implement IStudent interface. This null object implementation will contain "Do-nothing behavior" just to eliminate NullReferenceException and remove the need for repetitive null-checking code.
public class NullStudent : IStudent
{
public string GetName()
{
return "Not found";
}
void IStudent.ToString()
{
Console.WriteLine("This student is not on the list");
}
}
We should also modify StudentFactory class to reflect the above changes like this
public class StudentFactory
{
public List<Student> students;
public StudentFactory()
{
students = new List<Student>()
{
new Student("Mohamed", 20),
new Student("Diya", 25),
new Student("Anas", 15),
new Student("Roaa", 12),
new Student("Rawan", 10)
};
}
public IStudent GetStudent(string name)
{
var student = this.students.FirstOrDefault(n => n.Name == name);
if (student != null)
return student;
return new NullStudent();
}
}
So instead of returning Null at the end, we make use of NullStudent class.
Lastly, for improving the readability of Main method in Program class, and by adding Null object implementation, we are now able to remove all repetitive null checking code to be like this.
static void Main(string[] args)
{
var studentFactory = new StudentFactory();
var s1 = studentFactory.GetStudent("Mohamed");
var s2 = studentFactory.GetStudent("Yasser");
var s3 = studentFactory.GetStudent("Henno");
var s4 = studentFactory.GetStudent("Rawan");
Console.WriteLine("Students List....");
s1.ToString();
s2.ToString();
s3.ToString();
s4.ToString();
}
So by using null object pattern, we eliminate the need for repetitive null checking code and we improve the readability of the code.
I hope that this article helps you to get a brief understanding of null object pattern and how it is implemented and how it helps in writing robust and clean code.
Top comments (1)
Good read, you made the pattern clear to me thanks π