Introducing the Pattern0:42
So let's begin with something very, very simple. Imagine that when you leave your home, you have all of these various things that you need to check. Is the alarm on? Are the lights off? Have you locked the doors? All of these things that you need to do when you leave your house. Okay, so let's see how we could use the chain of responsibility pattern to illustrate this flow. Let's begin by creating some classes that will ultimately be subclasses. So we'll have something like Locks, and we'll have Lights, and then let's do one more.
So we'll have something like class Locks, and we'll have class Lights, and then let's do one more. How about class Alarm? Now, think of it like this. These classes are responsible for ensuring that the home is in the proper order for their given responsibilities. So this guy will make sure that the Alarm is on. This guy will make sure the Lights are off. And finally, this one will make sure that the Locks are all locked. Okay, let's start at the top.
Creating Check Entry Point1:32
And finally, this one will make sure that the doors are all locked. Okay, let's start at the top. So how can we do that using the chain of responsibility pattern? Well, it stands to reason that we'll have some kind of method called check, and presumably we're going to receive a request. So we need to make sure that we pass that through. Let's go ahead and try that at the bottom. Remember, down here would be the entry point, or the client, or the controller, possibly. So let's say new Locks. And then I can say locks->check().
Building HomeStatus DTO2:00
So let's say new Locks. And then I can say locks check. And for the request, maybe we'll have a really, really simple DTO here. So we'll say homeStatus. And this object will represent the current state of the home. And to keep it quick, we're just going to use all public properties here. So let's say, is the alarm on? true. And next one, are the doors locked? And then finally, are the lights off?
And next one, are the doors locked? And then finally, are the lights off? And in a perfect scenario, all of these will be true. Okay, so now we'll say status equals new HomeStatus. And that is what we will pass to our locks object. And of course, remember, we're hard coding this here, but presumably that could come from a form or a database result set, anything like that. So if we scroll up, now I know that this will receive a type of HomeStatus. And we'll call that home. So we can say, if the home is not locked, well, that's a problem.
Defining Base HomeChecker3:17
In that case, we're good to go. Nothing else to do here. Well, now we need to have some way to call the next object in the chain. So once again, using chain of responsibility, here's what that might look like. We would inherit from a base class. And why don't we call that HomeChecker, maybe something like that. All right, so now I want to make sure that all three of these classes adhere to the same contract. And that contract is that they offer a method called check. So why don't we just add that here, like so. public abstract function check.
So why don't we just add that here, like so. public abstract function check. Now, as long as we make sure that these implement or extend the abstract class in this case, extends HomeChecker, we can make sure that this contract has been enforced. But next, what about that thing where we want one object to call the next object in the chain? Well, why don't we place a next method directly on this parent class? Now, that will expect any class of type HomeChecker. And why don't we call that home? Okay, so now we have this method, but I still have no reference to what is essentially the successor, the next object in the chain that we should call.
Okay, so now we have this method, but I still have no reference to what is essentially the successor, the next object in the chain that we should call. So before we move ahead with this, let's add another method. And in terms of naming, most books will show you something like this, setSuccessor. But if you don't like the convention of using set, you could always say something like succeedWith. It doesn't matter. So once again, we'll expect an object of this same type, and we'll call it successor. Now I can assign that to this object. This successor equals successor.
Now I can assign that to this object. This successor equals successor. And we'll set that as protected at the top. Okay, so now whenever we call succeed with, we're going to give the object the next one in the chain that should be triggered. So that means when we call next here, and by the way, that should be homeStatus, not homeChecker. Anyhow, when we call this method, all we have to do is say this.successor, check, and then we pass through the home object. I hope that makes sense.
we pass through the home object. I hope that makes sense. And if it doesn't, when we start writing the client code, I think you'll understand it a little bit more. So now that we've implemented this, if I go back to locks, if the doors are in fact locked, we have nothing to do here, so we can defer to the next object in the chain, like this. This, next, and we pass through the home status. So let's go through what happens here. Well, we defer to the successor object that we set, and we call that check method again. And remember, because we declared that each of these objects must offer a check method,
Linking the Chain6:56
Our implementation is done. So now let's refer to our execution right here. We have our lock object. Let's do another one for the lights. And then one final one for the alarm. Now here's the key. We need to make sure that we set the successor on each object, like this. locks succeed with lights. And then if I duplicate this, lights succeed with alarm. That's how we create that chain.
Testing and Recap9:46
And that's why it's really important that we perform a quick check. Because in this case, the alarm does not have a successor. So if this conditional wasn't here, an error would, of course, be thrown. All right, so we've gone over it. Why don't we test this out from the terminal? Now remember, in this case, we are assuming that we've done everything correctly. So that means if we run this from the terminal, we shouldn't get anything. No exceptions were thrown. But now let's say that the doors are not locked, which we will represent with a Boolean. Well, we run it, and we get an exception.
We run it, and now we get the third exception. But also, we can mix and match. So let's assume that we just didn't do anything at all. Well, now when we run this, we will basically abort immediately. But as we fix these, it will continue on to the next ring in the chain, and then they will have the opportunity of either handling the request or deferring to the next object in the chain. All right, and really, that's all there is to this pattern. It's really not the most complicated thing in the world. Really, this abstract class here is sort of the bulk of it.
It's really not the most complicated thing in the world. Really, this abstract class here is sort of the bulk of it. We need to make sure that each class has the option of handling the request. So you perform some kind of check that determines, is this something that I can handle? If it is, then handle it, and we will never execute anything else within the chain. That's what makes this unique. However, if it can't handle it, then it defers to the next object in the chain. And now in this case, Lights will have the opportunity to handle the request. If it can, it does, and if it can't, it defers once again.
