 
                스위프트로 작성된 책임 연쇄
책임 연쇄 패턴은 핸들러 중 하나가 요청을 처리할 때까지 핸들러들의 체인(사슬)을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴입니다.
이 패턴은 발신자 클래스를 수신자들의 구상 클래스들에 연결하지 않고도 여러 객체가 요청을 처리할 수 있도록 합니다. 체인은 표준 핸들러 인터페이스를 따르는 모든 핸들러와 런타임 때 동적으로 구성될 수 있습니다.
복잡도:
인기도:
사용 예시들: 책임 연쇄 패턴은 스위프트 코드에 매우 일반적이며, 당신의 코드가 필터, 이벤터 체인 등과 같은 객체 체인과 함께 작동할 때 특히 유용합니다.
식별: 패턴의 모든 객체는 공통 인터페이스를 따르며, 다른 객체들의 같은 메서드들을 간접적으로 호출하는 한 객체 그룹의 행동 메서드들이 있습니다.
개념적인 예시
이 예시는 책임 연쇄 패턴의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
- 이 클래스들은 어떤 역할을 하나요?
- 패턴의 요소들은 어떻게 서로 연관되어 있나요?
이 패턴의 구조를 배우면 실제 스위프트 사용 사례를 기반으로 하는 다음 예시를 더욱 쉽게 이해할 수 있을 것입니다.
Example.swift: 개념적인 예시
import XCTest
/// The Handler interface declares a method for building the chain of handlers.
/// It also declares a method for executing a request.
protocol Handler: AnyObject {
    @discardableResult
    func setNext(handler: Handler) -> Handler
    func handle(request: String) -> String?
    var nextHandler: Handler? { get set }
}
extension Handler {
    func setNext(handler: Handler) -> Handler {
        self.nextHandler = handler
        /// Returning a handler from here will let us link handlers in a
        /// convenient way like this:
        /// monkey.setNext(handler: squirrel).setNext(handler: dog)
        return handler
    }
    func handle(request: String) -> String? {
        return nextHandler?.handle(request: request)
    }
}
/// All Concrete Handlers either handle a request or pass it to the next handler
/// in the chain.
class MonkeyHandler: Handler {
    var nextHandler: Handler?
    func handle(request: String) -> String? {
        if (request == "Banana") {
            return "Monkey: I'll eat the " + request + ".\n"
        } else {
            return nextHandler?.handle(request: request)
        }
    }
}
class SquirrelHandler: Handler {
    var nextHandler: Handler?
    func handle(request: String) -> String? {
        if (request == "Nut") {
            return "Squirrel: I'll eat the " + request + ".\n"
        } else {
            return nextHandler?.handle(request: request)
        }
    }
}
class DogHandler: Handler {
    var nextHandler: Handler?
    func handle(request: String) -> String? {
        if (request == "MeatBall") {
            return "Dog: I'll eat the " + request + ".\n"
        } else {
            return nextHandler?.handle(request: request)
        }
    }
}
/// The client code is usually suited to work with a single handler. In most
/// cases, it is not even aware that the handler is part of a chain.
class Client {
    // ...
    static func someClientCode(handler: Handler) {
        let food = ["Nut", "Banana", "Cup of coffee"]
        for item in food {
            print("Client: Who wants a " + item + "?\n")
            guard let result = handler.handle(request: item) else {
                print("  " + item + " was left untouched.\n")
                return
            }
            print("  " + result)
        }
    }
    // ...
}
/// Let's see how it all works together.
class ChainOfResponsibilityConceptual: XCTestCase {
 
    func test() {
        /// The other part of the client code constructs the actual chain.
        let monkey = MonkeyHandler()
        let squirrel = SquirrelHandler()
        let dog = DogHandler()
        monkey.setNext(handler: squirrel).setNext(handler: dog)
        /// The client should be able to send a request to any handler, not just
        /// the first one in the chain.
        print("Chain: Monkey > Squirrel > Dog\n\n")
        Client.someClientCode(handler: monkey)
        print()
        print("Subchain: Squirrel > Dog\n\n")
        Client.someClientCode(handler: squirrel)
    }
}
Output.txt: 실행 결과
Chain: Monkey > Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut.
Client: Who wants a Banana?
Monkey: I'll eat the Banana.
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.
Subchain: Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut.
Client: Who wants a Banana?
Banana was left untouched.
실제 사례 예시
Example.swift: 실제 사례 예시
import Foundation
import UIKit
import XCTest
protocol Handler {
    var next: Handler? { get }
    func handle(_ request: Request) -> LocalizedError?
}
class BaseHandler: Handler {
    var next: Handler?
    init(with handler: Handler? = nil) {
        self.next = handler
    }
    func handle(_ request: Request) -> LocalizedError? {
        return next?.handle(request)
    }
}
class LoginHandler: BaseHandler {
    override func handle(_ request: Request) -> LocalizedError? {
        guard request.email?.isEmpty == false else {
            return AuthError.emptyEmail
        }
        guard request.password?.isEmpty == false else {
            return AuthError.emptyPassword
        }
        return next?.handle(request)
    }
}
class SignUpHandler: BaseHandler {
    private struct Limit {
        static let passwordLength = 8
    }
    override func handle(_ request: Request) -> LocalizedError? {
        guard request.email?.contains("@") == true else {
            return AuthError.invalidEmail
        }
        guard (request.password?.count ?? 0) >= Limit.passwordLength else {
            return AuthError.invalidPassword
        }
        guard request.password == request.repeatedPassword else {
            return AuthError.differentPasswords
        }
        return next?.handle(request)
    }
}
class LocationHandler: BaseHandler {
    override func handle(_ request: Request) -> LocalizedError? {
        guard isLocationEnabled() else {
            return AuthError.locationDisabled
        }
        return next?.handle(request)
    }
    func isLocationEnabled() -> Bool {
        return true /// Calls special method
    }
}
class NotificationHandler: BaseHandler {
    override func handle(_ request: Request) -> LocalizedError? {
        guard isNotificationsEnabled() else {
            return AuthError.notificationsDisabled
        }
        return next?.handle(request)
    }
    func isNotificationsEnabled() -> Bool {
        return false /// Calls special method
    }
}
enum AuthError: LocalizedError {
    case emptyFirstName
    case emptyLastName
    case emptyEmail
    case emptyPassword
    case invalidEmail
    case invalidPassword
    case differentPasswords
    case locationDisabled
    case notificationsDisabled
    var errorDescription: String? {
        switch self {
        case .emptyFirstName:
            return "First name is empty"
        case .emptyLastName:
            return "Last name is empty"
        case .emptyEmail:
            return "Email is empty"
        case .emptyPassword:
            return "Password is empty"
        case .invalidEmail:
            return "Email is invalid"
        case .invalidPassword:
            return "Password is invalid"
        case .differentPasswords:
            return "Password and repeated password should be equal"
        case .locationDisabled:
            return "Please turn location services on"
        case .notificationsDisabled:
            return "Please turn notifications on"
        }
    }
}
protocol Request {
    var firstName: String? { get }
    var lastName: String? { get }
    var email: String? { get }
    var password: String? { get }
    var repeatedPassword: String? { get }
}
extension Request {
    /// Default implementations
    var firstName: String? { return nil }
    var lastName: String? { return nil }
    var email: String? { return nil }
    var password: String? { return nil }
    var repeatedPassword: String? { return nil }
}
struct SignUpRequest: Request {
    var firstName: String?
    var lastName: String?
    var email: String?
    var password: String?
    var repeatedPassword: String?
}
struct LoginRequest: Request {
    var email: String?
    var password: String?
}
protocol AuthHandlerSupportable: AnyObject {
    var handler: Handler? { get set }
}
class BaseAuthViewController: UIViewController, AuthHandlerSupportable {
    /// Base class or extensions can be used to implement a base behavior
    var handler: Handler?
    init(handler: Handler) {
        self.handler = handler
        super.init(nibName: nil, bundle: nil)
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
class LoginViewController: BaseAuthViewController {
    func loginButtonSelected() {
        print("Login View Controller: User selected Login button")
        let request = LoginRequest(email: "smth@gmail.com", password: "123HardPass")
        if let error = handler?.handle(request) {
            print("Login View Controller: something went wrong")
            print("Login View Controller: Error -> " + (error.errorDescription ?? ""))
        } else {
            print("Login View Controller: Preconditions are successfully validated")
        }
    }
}
class SignUpViewController: BaseAuthViewController {
    func signUpButtonSelected() {
        print("SignUp View Controller: User selected SignUp button")
        let request = SignUpRequest(firstName: "Vasya",
                                    lastName: "Pupkin",
                                    email: "vasya.pupkin@gmail.com",
                                    password: "123HardPass",
                                    repeatedPassword: "123HardPass")
        if let error = handler?.handle(request) {
            print("SignUp View Controller: something went wrong")
            print("SignUp View Controller: Error -> " + (error.errorDescription ?? ""))
        } else {
            print("SignUp View Controller: Preconditions are successfully validated")
        }
    }
}
class ChainOfResponsibilityRealWorld: XCTestCase {
    func testChainOfResponsibilityRealWorld() {
        print("Client: Let's test Login flow!")
        let loginHandler = LoginHandler(with: LocationHandler())
        let loginController = LoginViewController(handler: loginHandler)
        loginController.loginButtonSelected()
        print("\nClient: Let's test SignUp flow!")
        let signUpHandler = SignUpHandler(with: LocationHandler(with: NotificationHandler()))
        let signUpController = SignUpViewController(handler: signUpHandler)
        signUpController.signUpButtonSelected()
    }
}
Output.txt: 실행 결과
Client: Let's test Login flow!
Login View Controller: User selected Login button
Login View Controller: Preconditions are successfully validated
Client: Let's test SignUp flow!
SignUp View Controller: User selected SignUp button
SignUp View Controller: something went wrong
SignUp View Controller: Error -> Please turn notifications on