“Chain Of Responsibility” implemented in Swift

Chain of Responsibility

  • concept
  • Example: Business Scenario 1
  • Example: Business Scenario 2

Concept

The Chain of Responsibility Pattern (Chain of Responsibility Pattern) is a behavioral design pattern used to continuously pass a task to multiple processors until it is processed. In Swift, you can implement the Chain of Responsibility pattern using classes.

Example: Business Scenario 1

We can implement a chain of responsibility for processing sales orders, with the following three handlers:

  • Sales Manager (SalesManager): Process orders with an order amount ranging from 0 to 1000.
  • Finance Department (FinanceDepartment): Process orders with an order amount ranging from 1000 to 5000.
  • General Manager: Handle orders with an order amount exceeding 5,000.

Here’s an implementation of the Chain of Responsibility pattern in Swift:

protocol OrderHandler {<!-- -->
    var next: OrderHandler? {<!-- -->get set}
    func handleOrder(amount: Double)
}

class SalesManager: OrderHandler {<!-- -->
    var next: OrderHandler?
    func handleOrder(amount: Double) {<!-- -->
        if amount < 100 {<!-- -->
            print("Small staff can already process goods worth \(amount)")
        } else {<!-- -->
            next?. handleOrder(amount: amount)
        }
    }
}

class FinanceDepartment: OrderHandler {<!-- -->
    var next: OrderHandler?
    func handleOrder(amount: Double) {<!-- -->
        if amount < 1000 {<!-- -->
            print("The chief financial officer can already handle goods worth \(amount)")
        } else {<!-- -->
            next?. handleOrder(amount: amount)
        }
    }
}

class GeneralManager: OrderHandler {<!-- -->
    var next: OrderHandler?
    func handleOrder(amount: Double) {<!-- -->
        if amount < 10000 {<!-- -->
            print("The general manager can already process goods worth \(amount)")
        } else {<!-- -->
            print("No one can handle this item")
        }
    }
}

Call method

 let salesManager = SalesManager()
        let financeDepartment = FinanceDepartment()
        let generalManager = GeneralManager()
        
        salesManager.next = financeDepartment
        financeDepartment. next = generalManager
        generalManager.next = nil
        
        financeDepartment. handleOrder(amount: 100.0)
        financeDepartment. handleOrder(amount: 20000.0)

In the above code, we define an OrderHandler protocol, which contains a handleOrder(amount:) method and an optional attribute next, which is used to build the chain of responsibility. For each order, each handler on the chain of responsibility can be called one by one until the order is processed.

The client operation is as follows:

  1. Create the SalesManager, FinanceDepartment, and GeneralManager objects and set up the next handler.
  2. Call the sales manager’s method to process the order.
  3. According to the order amount, the processors on the chain of responsibility are called one by one until a processor capable of processing the order is found.

Example: Business Scenario 2

Take the example of handling user login as an example
We will create a user login process based on the Chain of Responsibility pattern, which involves the following steps:

  • Determine whether the username exists.
  • Determine whether the user password is correct.
  • Whether two-step verification or two-step verification is enabled.
  • Whether all security checks have passed, allowing the user to log in.

If a step does not complete successfully, subsequent steps will not be executed.

Implement as follows:

// chain of responsibility for handling user requests
protocol LoginHandler {<!-- -->
    var next: LoginHandler? {<!-- --> get set }
    func handle(request: LoginRequest)
}

// User login request
struct LoginRequest {<!-- -->
    let username: String
    let password: String
    var isTwoFactorAuthEnabled: Bool = false
    var isSecurityCheckPassed: Bool = false
}

// Processor to determine whether the username exists
class CheckUsernameHandler: LoginHandler {<!-- -->
    var next: LoginHandler?
    
    func handle(request: LoginRequest) {<!-- -->
        if request.username.count == 0 {<!-- -->
            print("Username cannot be empty")
        } else {<!-- -->
            next?.handle(request: request)
        }
    }
}

// Processor to determine whether the user password is correct
class CheckPasswordHandler: LoginHandler {<!-- -->
    var next: LoginHandler?
    
    func handle(request: LoginRequest) {<!-- -->
        if request.password.count == 0 {<!-- -->
            print("Password cannot be empty")
        } else {<!-- -->
            next?.handle(request: request)
        }
    }
}

// Two-step verification or two-step verification handler
class TwoFactorAuthHandler: LoginHandler {<!-- -->
    var next: LoginHandler?
    
    func handle(request: LoginRequest) {<!-- -->
        if request.isTwoFactorAuthEnabled {<!-- -->
            print("Please pass double verification or two-step verification")
        } else {<!-- -->
            next?.handle(request: request)
        }
    }
}

// safety detection handler
class SecurityCheckHandler: LoginHandler {<!-- -->
    var next: LoginHandler?
    
    func handle(request: LoginRequest) {<!-- -->
        if request.isSecurityCheckPassed {<!-- -->
            print("Successful login")
        } else {<!-- -->
            print("Security check failed, please contact administrator")
        }
    }
}

Call method

let checkUsernameHandler = CheckUsernameHandler()
let checkPasswordHandler = CheckPasswordHandler()
let twoFactorAuthHandler = TwoFactorAuthHandler()
let securityCheckHandler = SecurityCheckHandler()

checkUsernameHandler.next = checkPasswordHandler
checkPasswordHandler.next = twoFactorAuthHandler
twoFactorAuthHandler. next = securityCheckHandler

let request1 = LoginRequest(username: "test", password: "", isTwoFactorAuthEnabled: false, isSecurityCheckPassed: false)
checkUsernameHandler.handle(request: request1)

let request2 = LoginRequest(username: "test", password: "test123", isTwoFactorAuthEnabled: true, isSecurityCheckPassed: false)
checkUsernameHandler.handle(request: request2)

let request3 = LoginRequest(username: "test", password: "test123", isTwoFactorAuthEnabled: false, isSecurityCheckPassed: true)
checkUsernameHandler. handle(request: request3)

In the above code, we first defined the LoginHandler chain-of-responsibility pattern, and then added four handlers in succession to check username, password, double-authentication or two-step verification, and security checks. Stops the chain of responsibility if the given login request fails any of the items.

The sample code for the client call is as follows:

  1. Create a LoginRequest object and call the handle(request:) method after creating the CheckUsernameHandler processor to start the chain of responsibility process.
  2. Each handler in the chain of responsibility is called one by one until one is found that can handle the request.
  3. If none of the items are passed, the chain of responsibility is stopped.

The output is as follows:

password can not be blank
Please pass two-step verification or two-step verification
login successful

This example simply demonstrates how to use the Chain of Responsibility pattern to handle processes in Swift.