Chapter 6. Behavioral Patterns – Chain of Responsibility and Command

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:

  • The chain of responsibility pattern
  • The command pattern

The chain of responsibility pattern

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.

Roles

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:

  • You want to decouple the sender of a request to its receiver, allowing other objects to handle the request too
  • Objects that can handle the request are part of a chain of work, the request passes from one object to another until at least one of these objects can handle it
  • You want to allow objects that can process requests to be ordered in a preferential sequence that can be reordered, without having any impact on the calling component

Design

The following diagram illustrates the generic representation of the chain of responsibility pattern:

Design

Participants

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.

Collaboration

The following sequence diagram illustrates the collaboration between objects:

Collaboration

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.

Illustration

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:

  • If the level (or priority) is DEBUG, then this will be handled by the standard output logger
  • If the level is INFO, then we will use the standard output logger and e-mail logger that will send an e-mail with the message
  • If the level is ERROR, then all the three loggers will handle the message: the standard output logger, e-mail logger, and error logger

As 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.

Implementation

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:

Implementation

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:

Implementation

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:

  • Mask: This variable is intrinsic to the object and will be set during the initialization of the handler. This variable will be used to compare its value against the level of the received request, which means that if the mask is less than or equal to the level, the object will be able to handle the request.
  • Next: This variable is also intrinsic to the object and this permits the chaining. This variable contains the next 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:

Implementation

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset