Chain Of Responsibility Design Pattern in iOS

Omar Saibaa
Stackademic
Published in
12 min readNov 6, 2023

--

The Chain of Responsibility design pattern is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.

The chain of responsibility pattern is often used in situations where there is a chain of command, such as in a military or corporate setting. It can also be used in situations where there are multiple ways to handle a request, such as in a web application where different components can handle different types of requests.

The chain of responsibility pattern consists of the following components:

  • Subject: The subject is the object that initiates the request.
  • Handler: The handler is an object that can process the request.
  • Chain of handlers: The chain of handlers is a linked list of handlers.
  • Request: The request is an object that contains the information that the subject needs to be processed.

The chain of responsibility pattern works as follows:

  1. The subject sends a request to the first handler in the chain.
  2. The first handler decides either to process the request or to pass it to the next handler in the chain.
  3. If the first handler processes the request, the request is complete.
  4. If the first handler passes the request to the next handler in the chain, the process repeats itself.

The request is processed until it reaches a handler that can process it or until it reaches the end of the chain.

Example 1: 👶

Imagine a web application that allows users to submit articles. When a user submits an article, the application needs to validate the article before it is published.

The application could use the chain of responsibility pattern to implement the article validation process. The chain could consist of the following handlers:

  • Content handler: This handler checks the content of the article for profanity and other inappropriate content.
  • Grammar handler: This handler checks the grammar and spelling of the article.
  • Plagiarism handler: This handler checks the article for plagiarism.

If any of the handlers find a problem with the article, they can reject the article and return an error message to the user. Otherwise, the article is published.

Example 2: 🍎

Scrum is an agile framework for developing products and services. Scrum teams typically consist of a Product Owner, Scrum Master, and Development Team.

The Scrum process can be explained by the chain of responsibility pattern as follows:

  • The Product Owner is the first handler in the chain. They are responsible for the vision and success of the product and have the final say on what features are built and how they are prioritized.
  • The Scrum Master is the second handler in the chain. They are responsible for ensuring that the Scrum team is following the Scrum process and that they are removing any impediments that are preventing the team from making progress.
  • The Development Team is the third handler in the chain. They are responsible for building the product and ensuring that it meets the requirements of the Product Owner.

When a new feature request is submitted, it is first passed to the Product Owner. The Product Owner decides whether to accept or reject the feature request. If the Product Owner accepts the feature request, they prioritize it and add it to the Product Backlog.

The Product Owner then works with the Scrum Master to help the Development Team to break down the feature request into smaller tasks and to create a Sprint Backlog. The Development Team then works on the Sprint Backlog items during the Sprint.

If the Development Team encounters any impediments during the Sprint, they escalate the impediment to the Scrum Master. The Scrum Master then works to remove the impediment so that the Development Team can continue working on the Sprint Backlog items.

Once the Development Team has completed all of the Sprint Backlog items, they present the completed work to the Product Owner and the Scrum Master during the Sprint Review. The Product Owner then decides whether to accept or reject the completed work. If the Product Owner accepts the completed work, it is added to the Increment.

The Scrum Master then facilitates a Sprint Retrospective meeting, where the Development Team, the Product Owner, and the Scrum Master reflect on the Sprint and identify areas for improvement.

The chain of responsibility pattern is a powerful design pattern that can be used to implement a variety of different systems. It is a flexible and reusable pattern that can help to make your code more modular and maintainable.

Let’s take an example of how to deal with a single feature step-by-step :

  • A new feature request is submitted to the Product Owner.
  • The Product Owner reviews the feature request and decides to accept it.
  • The Product Owner prioritizes the feature request and adds it to the Product Backlog.
  • During the Sprint Planning meeting, the Scrum Master and Development Team work together to select the feature request from the Product Backlog and to break it down into smaller tasks.
  • The Development Team adds the tasks to the Sprint Backlog and begins working on them.
  • During the Sprint, the Development Team encounters an impediment. They escalate the impediment to the Scrum Master.
  • The Scrum Master works to remove the impediment so that the Development Team can continue working on the Sprint Backlog items.
  • At the end of the Sprint, the Development Team presents the completed work to the Product Owner and the Scrum Master during the Sprint Review.
  • The Product Owner accepts the completed work.
  • The Scrum Master facilitates a Sprint Retrospective meeting, where the Development Team, the Product Owner, and the Scrum Master reflect on the Sprint and identify areas for improvement.

In this example, the Product Owner, Scrum Master, and Development Team are all handlers in the chain of responsibility. The feature request is passed from the Product Owner to the Scrum Master to the Development Team. Each handler decides whether to process the request or to pass it to the next handler in the chain.

Example 3: 💻

Imagine a restaurant with a chain of responsibility for taking and fulfilling food orders.

  • Subject: The customer is the subject. They initiate the request by ordering food.
  • Handler: The waiter is the handler. They receive the request from the customer and decide whether to process it themselves or pass it to the next handler in the chain.
  • Chain of handlers: The chain of handlers is the kitchen staff. The waiter passes the request to the kitchen staff, who are responsible for preparing the food.
  • Request: The request is the food order.

The chain of responsibility pattern work as follow:

1. The subject sends a request to the first handler in the chain.
In the restaurant example, the customer sends their food order to the waiter.

2. The first handler decides either to process the request or to pass it to the next handler in the chain.
The waiter can either take the food order directly to the kitchen or pass it to another waiter.

3. If the first handler processes the request, the request is complete.
If the waiter takes the food order directly to the kitchen, the request is complete.

4. If the first handler passes the request to the next handler in the chain, the process repeats itself.
If the waiter passes the food order to another waiter, the process repeats itself until the food order reaches the kitchen staff.

How to implement the chain of responsibility pattern in iOS?

  1. Create a protocol for the handlers. This protocol should define a method for handling requests.
  2. Create concrete handler classes that implement the protocol. Each handler should be responsible for handling a specific type of request.
  3. Create a chain of handlers. This can be done by linking the handlers together in a linked list or using a tree structure.
  4. Pass the request to the first handler in the chain. The handler will then decide whether to process the request itself or pass it to the next handler in the chain.
  5. Continue passing the request to the next handler in the chain until it is processed or the end of the chain is reached.

Here is a simple example of how to implement the Chain of Responsibility pattern in iOS:

protocol RequestHandler {
func handle(request: Request) -> Bool
}

class Request {
var type: String
var data: Any

init(type: String, data: Any) {
self.type = type
self.data = data
}
}

class ConcreteRequestHandler1: RequestHandler {
func handle(request: Request) -> Bool {
if request.type == "my-type-1" {
// Process the request.
return true
} else {
return false
}
}
}

class ConcreteRequestHandler2: RequestHandler {
func handle(request: Request) -> Bool {
if request.type == "my-type-2" {
// Process the request.
return true
} else {
return false
}
}
}

class ChainOfResponsibility {
private var firstHandler: RequestHandler?

func addHandler(handler: RequestHandler) {
if firstHandler == nil {
firstHandler = handler
} else {
var currentHandler = firstHandler
while currentHandler?.nextHandler != nil {
currentHandler = currentHandler?.nextHandler
}
currentHandler?.nextHandler = handler
}
}

func handle(request: Request) -> Bool {
return firstHandler?.handle(request: request) ?? false
}
}

To use the ChainOfResponsibility class, you would first create a chain of handlers. You can do this by adding handlers to the chain using the addHandler() method.

Once you have created a chain of handlers, you can pass requests to the chain using the handle() method. The chain will then pass the request to the first handler in the chain. The handler will then decide whether to process the request itself or pass it to the next handler in the chain.

This process will continue until the request is processed or the end of the chain is reached.

Here is an example of how to use the ChainOfResponsibility class:

let chainOfResponsibility = ChainOfResponsibility()

// Add handlers to the chain.
chainOfResponsibility.addHandler(handler: ConcreteRequestHandler1())
chainOfResponsibility.addHandler(handler: ConcreteRequestHandler2())

// Pass a request to the chain.
let request = Request(type: "my-type-1", data: "some-data")

// The chain will process the request.
let response = chainOfResponsibility.handle(request: request)

if response {
// The request was processed successfully.
} else {
// The request was not processed successfully.
}

Example 4: 💪

In a typical iOS app, there are many different types of events that can occur, such as user interface events, network events, and sensor events. It can be difficult and cumbersome to handle all of these events directly in the app’s main codebase.

The Chain of Responsibility design pattern provides a way to handle events in a loose-coupled and scalable manner. The pattern works by defining a chain of handlers, each of which is responsible for handling a specific type of event. When an event occurs, the app passes the event to the first handler in the chain. The handler then decides whether to handle the event itself or to pass it to the next handler in the chain. This process continues until the event is handled by one of the handlers in the chain.

The next code shows how to use the Chain of Responsibility design pattern to handle user interface events in an iOS app:

// Define the different types of user interface events that can occur.
enum UserInterfaceEvent {
case buttonTapped
case textFieldChanged
case tableViewCellSelected
}

// Define a protocol for the handlers that will handle the user interface events.
protocol UserInterfaceEventHandler {
func handleEvent(event: UserInterfaceEvent)
}

// Define a concrete handler for each type of user interface event.
class ButtonTappedHandler: UserInterfaceEventHandler {
func handleEvent(event: UserInterfaceEvent) {
if event == .buttonTapped {
// Handle the button tapped event.
}
}
}

class TextFieldChangedHandler: UserInterfaceEventHandler {
func handleEvent(event: UserInterfaceEvent) {
if event == .textFieldChanged {
// Handle the text field changed event.
}
}
}

class TableViewCellSelectedHandler: UserInterfaceEventHandler {
func handleEvent(event: UserInterfaceEvent) {
if event == .tableViewCellSelected {
// Handle the table view cell selected event.
}
}
}

// Define a chain of responsibility for the user interface events.
class UserInterfaceEventChainOfResponsibility {
private var handlers = [UserInterfaceEventHandler]()

func registerHandler(handler: UserInterfaceEventHandler) {
handlers.append(handler)
}

func handleEvent(event: UserInterfaceEvent) {
for handler in handlers {
handler.handleEvent(event: event)
// Break the chain if the event has been handled.
if event.isHandled {
break
}
}
}
}

// Usage

// Create a user interface event chain of responsibility.
let userInterfaceEventChainOfResponsibility = UserInterfaceEventChainOfResponsibility()

// Register the handlers for the different types of user interface events.
userInterfaceEventChainOfResponsibility.registerHandler(handler: ButtonTappedHandler())
userInterfaceEventChainOfResponsibility.registerHandler(handler: TextFieldChangedHandler())
userInterfaceEventChainOfResponsibility.registerHandler(handler: TableViewCellSelectedHandler())

// Handle a user interface event.
let event = UserInterfaceEvent.buttonTapped

// Pass the event to the user interface event chain of responsibility.
userInterfaceEventChainOfResponsibility.handleEvent(event: event)

Example 5: 🐉

The Twitter app needs to handle a variety of different events, such as the user tapping on a tweet, the user liking a tweet, or the user retweeting a tweet. The Twitter app does not want to have to keep track of all of these events and handle them directly. This would be inefficient and would not scale well.

The Twitter app uses the Chain of Responsibility pattern to solve this problem. The app has a series of handlers, each of which is responsible for handling a different type of event. When an event occurs, the app passes the event to the first handler in the chain. The handler then decides whether to handle the event itself or to pass it to the next handler in the chain. This process continues until the event is handled by one of the handlers in the chain.

Code:

protocol TwitterEventHandler {
func handleEvent(event: TwitterEvent)
}

class TwitterTweetTappedHandler: TwitterEventHandler {
func handleEvent(event: TwitterEvent) {
if event is TweetTappedEvent {
// Handle the tweet tapped event.
}
}
}

class TwitterLikeTweetHandler: TwitterEventHandler {
func handleEvent(event: TwitterEvent) {
if event is LikeTweetEvent {
// Handle the like tweet event.
}
}
}

class TwitterRetweetTweetHandler: TwitterEventHandler {
func handleEvent(event: TwitterEvent) {
if event is RetweetTweetEvent {
// Handle the retweet tweet event.
}
}
}

class TwitterEventChainOfResponsibility {
private var handlers = [TwitterEventHandler]()

func registerHandler(handler: TwitterEventHandler) {
handlers.append(handler)
}

func handleEvent(event: TwitterEvent) {
for handler in handlers {
handler.handleEvent(event: event)
// Break the chain if the event has been handled.
if event.isHandled {
break
}
}
}
}

// Usage

let eventChainOfResponsibility = TwitterEventChainOfResponsibility()

// Register handlers.
eventChainOfResponsibility.registerHandler(handler: TwitterTweetTappedHandler())
eventChainOfResponsibility.registerHandler(handler: TwitterLikeTweetHandler())
eventChainOfResponsibility.registerHandler(handler: TwitterRetweetTweetHandler())

// Handle an event.
let event = TweetTappedEvent()
eventChainOfResponsibility.handleEvent(event: event)

Advantages of Chain of Responsibility pattern in iOS:

  • Loose coupling: The Chain of Responsibility pattern promotes loose coupling between objects. This means that objects are loosely coupled when they have minimal knowledge about each other’s internal implementation. This makes the system more flexible and scalable.
  • Reusability: The Chain of Responsibility pattern is reusable because it can be used in a variety of different situations. For example, the Chain of Responsibility pattern can be used to implement a user interface event handling system, a data validation system, or a security system.
  • Scalability: The Chain of Responsibility pattern is scalable because it can handle any number of handlers. This makes it ideal for systems that need to be able to handle a large number of requests.
  • Performance: The Chain of Responsibility pattern can improve performance by avoiding the need for conditional statements to determine which object is responsible for handling a request. Instead, the request is simply passed to the first handler in the chain, and the chain handles the rest.

Disadvantages of Chain of Responsibility pattern in iOS:

  • Complexity: The Chain of Responsibility pattern can become complex if there are a large number of handlers in the chain. This can make it difficult to understand and maintain the system.
  • Order dependency: The Chain of Responsibility pattern can be order-dependent, meaning that the order in which the handlers are linked together can affect the way that requests are processed. This can make it difficult to debug the system if there are problems with the order of the handlers.
  • Performance overhead: The Chain of Responsibility pattern can add some performance overhead, because each handler in the chain needs to check whether it is responsible for handling the request before passing it to the next handler.

Tips for using Chain of Responsibility pattern in iOS:

  • Use the pattern when there is a clear chain of responsibility:
    This means that there is a natural order in which requests should be processed. For example, a user interface event handling system would have a clear chain of responsibility, with the event first being handled by the view that received the event, and then being passed up the view hierarchy until it is handled by a view that is responsible for handling the event.
  • Avoid using the pattern if there are a large number of handlers in the chain:
    This is because the Chain of Responsibility pattern can become complex and difficult to maintain if there are a large number of handlers. If there are a large number of handlers, it may be better to use a different design pattern, such as the Mediator pattern.
  • Carefully consider the order in which the handlers are linked together:
    The order in which the handlers are linked together can affect the way that requests are processed. For example, if a view hierarchy has a button and a text field, and the button is linked before the text field in the Chain of Responsibility, then the button will be responsible for handling all events before the text field has a chance to handle them.
  • Test the system thoroughly to ensure that requests are being processed correctly:
    The Chain of Responsibility pattern can be difficult to debug, so it is important to test the system thoroughly to ensure that requests are being processed correctly.

Finally:

The Chain of Responsibility design pattern is a powerful design pattern that can be used to implement a variety of different systems in iOS. It is a flexible and reusable pattern, but it can become complex if there are a large number of handlers in the chain. It is important to carefully consider the order in which the handlers are linked together and to test the system thoroughly to ensure that requests are being processed correctly.

Thanks for reading and Don’t forget to claps, comment and follow me for more.

Stackademic

Thank you for reading until the end. Before you go:

  • Please consider clapping and following the writer! 👏
  • Follow us on Twitter(X), LinkedIn, and YouTube.
  • Visit Stackademic.com to find out more about how we are democratizing free programming education around the world.

--

--