Lecture thumbnail 0:00 / 8:53 Why does a null object exist?
Well, let me show you an example.
Let’s suppose that you decide that you’re making a bank account.
So the typical bank account class that we’ve done like a dozen times before, but this time round you’re
adding logging to it.
So essentially you have some sort of interface.
Let’s have an interface called I Log and this interface Capital L here, this interface can actually
write logging information to wherever you want, like to the console or send text messages, whatever.
Now we’ll have two methods in the log in.
We’ll have the info method for writing a string of just information, and then you’ll have a more severe
method like warn, for example, to write a warning about something that happens.
So now if you have this implementation, you can, for example, make a console log.
So if you make a derived type, let’s console log.
This is where you can implement both of these members and you can just write something to the console.
So in the case of info, you can just write line the message or in the case of warning, you can write
line something scary like you can prepend it with a capital warning and then write the message in here.
So this is how you typically implement the interface.
And now if you have a bank account, for example, which relies on all of this, then you can implement
this using the I log.
And once again, the I log can be injected if you’re using a dependency injection container.
So you have I log log, for example.
You can also have inner information like the balance of the account.
And once again, you can so construct the account, giving it a particular log and everything is fine
for now.
So we have a dependency on the log and now we can use it.
So whenever somebody, for example, deposits a certain amount of money, you can not only increment
the balance, so you can say balance plus equals the amount, but you can also perform logging, you
can say log.info and you can write some information like deposited this amount and the balance is now
balance something like this.
So now if you go ahead and you actually use it, you need to make a log for all of this to work.
So you say var log equals new console log like so, and then you make bank account.
So var equals new bank account.
There we go.
And you have to specify the log here.
That’s the argument.
And then of course you can go ahead and deposit $100, for example.
So if we go ahead and execute this well, predictably enough, this is going to write the information
to the console.
However, let’s imagine that you’re already given this interface with a bank account and you cannot
change the bank account.
So once again, this open closed principle, you cannot go back into bank account and you cannot change
things in here.
Now let’s suppose that you decide you don’t want to do any logging, so what can you do?
Can you pass null in here?
Well, you can try passing null and if you execute this then well obviously you’re going to get an exception
null reference exception because some of these attempting to assign it, it’s actually you don’t even
get the exception when you try to log something, you get it in the constructor because we generated
the constructor which throws an argument null exception some of passes in and now let’s actually get
rid of this for now.
So let’s just assign log whatever it is and I execute it once again.
And now we’re going to get an exception inside the deposit method because of course we tried to call
log, which is null.
So fairly sensible stuff.
Now there is a question here of how would a typical API creator handle the situation where somebody
passes in a null?
So for example, you can be explicit about it.
You can say, well, we’re going to allow now we’re going to basically say something like can be null
here.
That’s a resharper annotation, by the way.
So I think that we need to get it from Nugget or somewhere.
Let’s actually get it from the nugget package.
So here we go.
Jetbrains Resharper annotations.
Just install it like so and hopefully we are done.
So now what we’ve done is we’ve specified that the argument can be null, so we’re kind of giving a
user a hint that, you know, you can’t provide null here.
And then of course if you have the eye log being null, then you would have to have every call as a
safe call.
So you would have to use this operator question mark, dot, in order to make sure that things are invoked
correctly.
And in this case, yeah, in this case everything will work.
But of course this requires upfront design.
And the problem with the upfront design, well actually there are two problems with this.
One problem is that you have to do everything upfront.
The other problem is that dependency injection doesn’t really enjoy the fact that you provide a null
as the default value for a component.
So it’s certainly possible to configure a component like bank account, for example, to be constructed
with a null instead of the type.
But typically dependency injection containers, they’re going to look for an I log parameter.
We can actually try and verify this so we can if we just get rid of this for a second, I’ll go ahead
and I’ll make a new container builder.
And then we can try and configure it and actually see what’s going on.
So the idea here is that we basically want to register the type of bank account, bank account.
There we go.
Bank account.
Not bank account.
And then using var C equals CB build.
So we build the container.
We can then try and see what’s going on.
Well, we say var bar equals C dot resolve bank account.
Okay, so we don’t have a registration right now.
And also we have to register something.
And the question is how?
Because I don’t think you can write CB register instance, for example, and register an instance of
null being cast to an I log.
This is not something that’s going to work, although we can try it, we can certainly try something
like that.
And I log type of null and see what happens here.
Well, as you can see, we get an exception at this point.
Value cannot be null, so you cannot really register a null.
So what can you do?
Well, you can try registering the bank account differently.
You can say CB register and you can say the here’s the context and we’re going to make a new bank account
explicitly providing the value null here.
So yeah, that will work.
That obviously will work because it just invokes whatever you put in the lambda.
So you can see we executed the whole thing.
It worked just fine.
However, let’s imagine that there is no upfront design.
Nobody does a can be null here.
There is no hint that I can be.
Now you’re even throwing maybe an exception in the constructor.
So you have to deal with this.
And this is where the null object pattern comes in.
So you have to provide a log which does absolutely nothing.
So one of the ways of doing it is you simply implement whatever interface is actually required.
So you can just make a new log which does nothing.
Let’s call it a null log.
So you make a null log and you implement the I log interface, Alt-enter implement the missing members
obviously, and then you just get rid of everything.
So you have the members which do absolutely nothing at all.
And then what you can do is coming back to the registration.
You can once again, you can register, register the type.
Normally, without all of this constructor magic, you can register the bank account type and in addition
you can register the null log.
So null log gets registered as an I log and that’s pretty much it.
And we can now do bar dot deposit 100 for example.
And if I execute this well, as you can see, everything works, everything works correctly and we get
no logging, there is no output because obviously we’re using a null log, but we don’t get any exceptions
either.
So this is how you implement the null object pattern.
So it’s very simple.
You simply make an object which conforms to the interface that you need and you simply leave out the
members.
So make sure that the members do absolutely nothing at all.
It gets a bit more complicated if you have properties though, like if you have the name of the log,
what will you put in the name?
Would you just do an auto property with name like this, or would you do something else?
It becomes a bit more problematic, but if you just have methods, the solution is fairly obvious.
If they don’t return anything, you just make them empty.
And if they return like an int for example, well, what can you do?
You can return a default event.
That’s the best case scenario that I can suggest, although in some cases it might actually be detrimental
and you might actually break things.
So a null object isn’t always it isn’t always safe to make a null object in the sense that sometimes
the null object state might be particularly important to its operation.
So you really have to kind of make a good judgement on whether it makes sense to construct a null object
or not.
Play Stop Play Play Start Play information alert