Chain of Responsibility Design Pattern in Swift


Greetings, traveler!

We continue to learn about Design Patterns. We have started to explore Behavioral Patterns, and the first one we will be looking at is the Chain of Responsibility Pattern.

The Chain of responsibility is a design pattern that allows requests to be processed sequentially by a series of handlers. Each handler decides whether or not to process the request based on its own criteria. If a handler chooses to process the request, it may pass it on to the next handler in the chain or decide to handle it. This allows for flexibility in handling requests and ensures that each handler can choose whether or not it wants to take on the responsibility of processing the request.

Example of usage

Let’s take a look at a simple example. Imagine we have an application where a user can be in multiple states. We need to process each state separately.

enum UserState {
    case acceptTerms, changePassword, blocked, `default`
}

To do this, we’ve created a protocol and managers to handle each state.

protocol UserStateManagerProtocol {
    var nextManager: UserStateManagerProtocol? { get }
    
    func handleState(_ state: UserState)
}

The manager receives the state as a parameter in the function and decides whether it can handle the state itself or if it needs to pass the decision down the chain of responsibility.


// 1.
final class DefaultUserStateManager: UserStateManagerProtocol {
    
    var nextManager: UserStateManagerProtocol?
    
    init(nextManager: UserStateManagerProtocol? = nil) {
        self.nextManager = nextManager
    }
    
    func handleState(_ state: UserState) {
        guard state == .default else {
            nextManager?.handleState(state)
            return
        }
        
        // Do nothing
    }
    
}

// 2.
final class ChangePasswordStateManager: UserStateManagerProtocol {
    
    var nextManager: UserStateManagerProtocol?
    
    init(nextManager: UserStateManagerProtocol? = nil) {
        self.nextManager = nextManager
    }
    
    func handleState(_ state: UserState) {
        guard state == .changePassword else {
            nextManager?.handleState(state)
            return
        }
        
        print("Please change your password.")
    }
    
}

// 3.
final class BlockedUserStateManager: UserStateManagerProtocol {
    
    var nextManager: UserStateManagerProtocol?
    
    init(nextManager: UserStateManagerProtocol? = nil) {
        self.nextManager = nextManager
    }
    
    func handleState(_ state: UserState) {
        guard state == .blocked else {
            nextManager?.handleState(state)
            return
        }
        
        print("Your profile is blocked.")
    }
    
}

// 4.
final class AcceptTermsStateManager: UserStateManagerProtocol {
    
    var nextManager: UserStateManagerProtocol?
    
    init(nextManager: UserStateManagerProtocol? = nil) {
        self.nextManager = nextManager
    }
    
    func handleState(_ state: UserState) {
        guard state == .acceptTerms else {
            nextManager?.handleState(state)
            return
        }
        
        print("Please accept the terms and conditions.")
    }
    
}

Conclusion

So, why should you consider using the Chain of Responsibility pattern in your software development? Well, imagine a scenario where your application needs to handle various requests in different ways, but you don’t know in advance which requests will come or which handlers will need to be used for them. The Chain of Responsibility pattern provides a dynamic solution to this challenge. It’s also a great tool when you need a set of objects to handle a request to be set up dynamically or when you want the handlers to be executed in a specific order.

I hope this was interesting and not too boring for you. The following article will explore another design pattern called the Command.