In this chapter, we will continue to explore the behavioral patterns—the chain of responsibility and command patterns. Both of these patterns are concerned with passing requests to appropriate objects that will then execute the action.
The main difference between these two patterns is the way that the requests are passed between objects.
In this chapter, we will discuss the following topics:
When you write an application, it may be that an event generated by an object needs to be handled by another object. You may also want the handle to be inaccessible by another object.
In this section, you will notice that the chain of responsibility pattern creates a chain of objects in such a way that if an object of this chain cannot handle the request, it sends the request to the next object, the successor, until one of them can handle the request.
This pattern allows an object to send a request without knowing which object will receive and handle it. The request is sent from one object to another, making them parts of a chain. Each object of this chain can handle the request, pass it to its successor, or do both.
You may want to use this pattern when:
The following diagram illustrates the generic representation of the chain of responsibility pattern:
This pattern has three participants, which are as follows:
AbstractHandler
: This defines the interface of the requests and implements the association of the chain of responsibility pattern.ConcreteHandlers
: These objects can handle requests that they are responsible for. If it cannot handle the request, it passes the request to its successor or stops the chain.Client
: The client sends the request to the first object of the chain that may handle the request.The following sequence diagram illustrates the collaboration between objects:
The client sends the request to the first object of the chain. Then, this request is propagated throughout the chain until at least one of the objects of the chain can handle it.
Suppose that you are supervising the development of a mobile application, and you want to handle some log messages differently, depending on the priority of the logger.
You define three types of priority, which means three levels of loggers: DEBUG
, INFO
, and ERROR
.
Depending on the level of the log messages, you can handle it as follows:
DEBUG
, then this will be handled by the standard output loggerINFO
, then we will use the standard output logger and e-mail logger that will send an e-mail with the messageERROR
, then all the three loggers will handle the message: the standard output logger, e-mail logger, and error loggerAs we can see here, we need to define a chain of objects in the following order: StdOutLogger
, EmailLogger
, and ErrorLogger
.
The client will then only call the first concrete handler, the class that may handle the request: StdOutLogger
.
To implement our pattern, we will need to prepare our abstract class first. Remember that with Swift, an abstract class does not really exist. We will write our abstract class as a class, but methods that need to be overridden will have the following statement:
preconditionFailure("Must be overridden")
In this case, if the code of the abstract class is called instead of the one available in the derived class, an exception similar to the following will be raised:
Let's now begin with the implementation.
First, open the ChainOfResponsibilityPattern
project that you will find in the Chapter 6
folder.
The project is organized with the following structure:
There's nothing complex here; we will define our chain and make a call to the main.swift
file. The abstract class is defined in the Logger.swift
file, and our three concreteHandlers
classes have their own respective Swift files.
We will define the abstract class as follows:
class Logger { static var ERROR = 1 static var INFO = 2 static var DEBUG = 3 var mask:Int? var next:Logger? func nextHandler(nextLogger:Logger) -> Logger? { next = nextLogger return next } func message(message: String, priority: Int){ if priority <= mask { writeMessage(message) if let next = next { next.message(message, priority: priority) } } } func writeMessage(message: String) { preconditionFailure("Must be overridden") } static func prepareDefaultChain() -> Logger? { var l: Logger? var l1: Logger? l = StdOutLogger(mask: Logger.DEBUG) l1 = l!.nextHandler(EmailLogger(mask: Logger.INFO)) l1 = l1!.nextHandler(ErrLogger(mask: Logger.ERROR)) return l }}
We define three static variables that will represent our different levels of logs: ERROR
, INFO
, and DEBUG
.
Then, we have two other variables that are declared, which are as follows:
ConcreteHandler
where the request will be passed.We have the following three functions:
func nextHandler(…)
: This is a function that allows you to assign the next concrete handler to the next
variable. Note that this function returns a logger. This is called nextLogger
.So, if we write the following statement:
l = StdOutLogger(mask: Logger.DEBUG) l1 = l!.nextHandler(EmailLogger(mask: Logger.INFO))
Then, l1
is an EmailLogger
instance and not a StdOutLogger
.
func message(…)
: This is the main function that has the responsibility (or not) to process the request and/or pass it to the next object of the chain.writeMessage(…)
: This function is called by the message(…)
function to simulate the work applied on the request. Here, we will only display a message that is linked to the current concrete handler object. Since we are in an abstract class, we add a preconditionfailure(…)
statement that will inform us that this
function must be overridden in the derived class. If the code is executed and the derived class does not override this
method, a fatal error will be raised, which is explained in the Implementation section of this pattern.prepareDefaultChain(…)
: This is a class function that encapsulates the creation of our default chain.Our abstract class is now ready; we have only to write our derived classes. Remember that the writeMessage(…)
function must be overridden, and we need to initialize the mask of our concrete handler.
First, let's take a look at the StdOutLogger
concrete handler, which is as follows:
class StdOutLogger: Logger { init(mask: Int) { super.init() self.mask = mask } override func writeMessage(message: String) { print("Sending to StdOutLogger: (message)") } }
Next, we have the EmailLogger
class:
class EmailLogger: Logger { init(mask: Int) { super.init() self.mask = mask } override func writeMessage(message: String) { print("Sending by Email: (message)") } }
In addition, we have the ErrLogger
class:
class ErrLogger: Logger { init(mask: Int) { super.init() self.mask = mask } override func writeMessage(message: String) { print("Sending to ErrorLogger: (message)") } }
All of our concrete handlers are now ready. It is time for us to write our test in the main.swift
file.
We first prepare our chain using the Logger
class function, prepareDefaultChain
:
print("Building the Chain") var l: Logger? l = Logger.prepareDefaultChain()
Then, we send a request (a string message with a logger type) to the first object of the chain (l
is StdOutLogger
):
print("- *** stdOutLogger:") // Handled by StdOutLogger l?.message("Entering the func Y()", priority: Logger.DEBUG) print("- StdOutLogger && EmailLogger:") // Handled by StdOutLogger && EmailLogger l?.message("Step 1 Completed", priority: Logger.INFO) print("- all three loggers:") // Handled by all Logger l?.message("An error occurred", priority: Logger.ERR)
Now, we will build and run the project. You will see the following result on the console:
The console output is very clear. The first handler has handled the first request only, and the second request has been handled by the StdOutLogger
class and the EmailLogger
class. The third request has been handled by all three handlers.