Published: 14 November 2017
Tagged: Pattern iOS Swift
Необходимо последовательно выполнить провероку над данными, а затем для них применить изменения. Причем порядок проверок и изменений хочется применять как угодно, в зависимости от ситуации. В этом нам на помощь приходит поведенческий паттерн «Цепочка обязанностей».
Например нам надо проверить строку с телефонным номером и отформатировать ее. Опишем по порядку какие действия мы хотим выполнить:
- Строка не должна быть пустой и не nil
- Строка должна содержать определенное количество символов
- Необходимо отформатировать строку с учетом указанного формата
Цепочка обязанностей — это поведенческий паттерн проектирования, который позволяет передавать запросы последовательно по цепочке обработчиков. Каждый последующий обработчик решает, может ли он обработать запрос сам и стоит ли передавать запрос дальше по цепи.
Playground с примером на Git-Hub
Реализация
-
Общий интерфейс для всех обработчиков MiddlewareProtocol:
protocol MiddlewareProtocol { // Связываем текущий и следующий экземпляр обработчика @discardableResult func link(with: MiddlewareProtocol) -> MiddlewareProtocol // Этот метод необходимо перегрузить, в нем описывается вся логика func check(value: MiddlewareItem) -> MiddlewareItem // Вспомогательный метод для упрощения вызова следующей проверки в цепочке func checkNext(value: MiddlewareItem) -> MiddlewareItem }
-
Добавим перечисление MiddlewareItem -- этот тип данных содержит само значение .value и ошибку .error если мы не прошли валидацию:
public enum MiddlewareItem { case value(String?) case error(String) }
-
Базовая реализация обработчика 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) } }
Все обработчики необходимо наследовать от Middleware
Теперь напишем конкретные обработчики для валидации и изменения данных, в нашем случае это строка:
- Обработчик проверки на nil и пустую строку: CheckNilMiddleware
- Обработчик проверки на длинну строки: CheckCountMiddleware
- Обработчик который форматирует строку с учетом указанного формата: PhoneFormatterMiddleware
Реализация всех трех классов описаных выше
Разберем подробнее
- Обработчик должен наследоваться от Middleware
- Перегружем метод check(value: MiddlewareItem) -> MiddlewareItem и описываем логику проверок, изменений над данными .value(let val).
- Если все успешно вызываем следующую проверку в нашем списке цепочек return checkNext(value: value)
- Если хотим прервать выполнение то сразу возвращаем ошибку return .error(“Example error text”).
- Если хотим подменить данные то передаем новое значение return checkNext(value: .value(“New value”))
override func check(value: MiddlewareItem) -> MiddlewareItem {
switch value {
case .value(let str):
if str == nil { // Выполняем проверку
// Валидация не прошла, возвращаем ошибку
return .error("Example error text")
}
default:
break
}
// Если все успешно вызываем следующую
// проверку в нашем списке цепочек
return checkNext(value: value)
}
Смотри комментарии к коду 👆
Использование
Проверяем и отформатируем телефонный номер с помощью нашей реализации:
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 ?? "")
}
Можно добавить немного функциональной магии, добавим оператор |>
precedencegroup ForwardOperator {
associativity: left
}
infix operator |>: ForwardOperator
func |> (lhs: MiddlewareProtocol, rhs: MiddlewareProtocol) -> MiddlewareProtocol {
return lhs.link(with: rhs)
}
// после можно использовать такое написание
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 ?? "")
}
Результатом выполнения будет вывод в консоль: +7 (963) 448 02-09
В заключении
Таким образом мы можем добавлять сколько угодно не зависящих друг от друга обработчиков и использовать их в зависимости от ситуации.
Где еще можно использовать:
- Редактор изображений. Добавить обработчики которые будут выполнять манипуляции над изображением, таким образом получим удобный способ применять фильтры
- Валидация при авторизации или регистрации пользователя