Published: 14 November 2017
Tagged: Pattern iOS Swift
You need to sequentially validate data and then apply transformations to it. The order of validations and transformations should be flexible, depending on the situation. The behavioral pattern “Chain of Responsibility” comes to the rescue.
For example, we need to validate a phone number string and format it. Let’s describe in order the actions we want to perform:
- The string must not be empty or nil
- The string must contain a specific number of characters
- The string must be formatted according to a specified format
Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Each handler decides whether to process the request itself or pass it further along the chain.
Playground with example on GitHub ![]()
Implementation
-
Common interface for all handlers MiddlewareProtocol:
protocol MiddlewareProtocol { // Link current and next handler instances @discardableResult func link(with: MiddlewareProtocol) -> MiddlewareProtocol // This method must be overridden — it contains all the logic func check(value: MiddlewareItem) -> MiddlewareItem // Helper method to simplify calling the next check in the chain func checkNext(value: MiddlewareItem) -> MiddlewareItem } -
Add the MiddlewareItem enum — this type holds the value itself .value and an error .error if validation fails:
public enum MiddlewareItem { case value(String?) case error(String) } -
Base implementation of MiddlewareProtocol:
class Middleware: MiddlewareProtocol { var next: MiddlewareProtocol? @discardableResult func link(with: MiddlewareProtocol) -> MiddlewareProtocol { self.next = with return with } func check(value: MiddlewareItem) -> MiddlewareItem { return checkNext(value: value) } func checkNext(value: MiddlewareItem) -> MiddlewareItem { guard let next = self.next else { return value } return next.check(value: value) } }
All handlers must inherit from Middleware
Now let’s write concrete handlers for validating and transforming data — in our case, a string:
- Handler for nil and empty string checks: CheckNilMiddleware
- Handler for string length checks: CheckCountMiddleware
- Handler that formats the string according to a specified format: PhoneFormatterMiddleware
Implementation of all three classes described above ![]()
Breakdown
- The handler must inherit from Middleware
- Override the method check(value: MiddlewareItem) -> MiddlewareItem and describe the validation/transformation logic for .value(let val).
- If everything succeeds, call the next check in the chain: return checkNext(value: value)
- To stop execution, immediately return an error: return .error(“Example error text”).
- To substitute data, pass a new value: return checkNext(value: .value(“New value”))
override func check(value: MiddlewareItem) -> MiddlewareItem {
switch value {
case .value(let str):
if str == nil { // Perform check
// Validation failed, return error
return .error("Example error text")
}
default:
break
}
// If everything is fine, call the next
// check in our chain
return checkNext(value: value)
}See code comments above 👆
Usage
Validate and format a phone number using our implementation:
let start = Middleware()
start
.link(with: CheckNilMiddleware())
.link(with: CheckCountMiddleware(length: 11))
.link(with: PhoneFormatterMiddleware(format: "+X (XXX) XXX XX-XX"))
let result = start.check(value: .value("79634480209")))
switch result {
case .error(let error): print(error)
case .value(let result): print(result ?? "")
}We can add a touch of functional magic with the |> operator:
precedencegroup ForwardOperator {
associativity: left
}
infix operator |>: ForwardOperator
func |> (lhs: MiddlewareProtocol, rhs: MiddlewareProtocol) -> MiddlewareProtocol {
return lhs.link(with: rhs)
}
// Then you can write it like this
let start = Middleware()
start
|> NilValidation()
|> CountValidation(length: 11)
|> PhoneFormatterValidation(format: "+X (XXX) XXX XX-XX")
switch start.check(value: .value("79634481259")) {
case let .error(error): print(error)
case let .value(result): print(result ?? "")
}The result printed to the console: +7 (963) 448 02-09
Conclusion
This way we can add as many independent handlers as we want and use them depending on the situation.
Other use cases:
- Image editor. Add handlers that perform manipulations on an image — a convenient way to apply filters.
- Validation during user login or registration.