【问题标题】:Swift Protocol referencing itself and associated type引用自身和关联类型的 Swift 协议
【发布时间】:2020-04-16 17:29:30
【问题描述】:

我有一个责任链模式的经典实现,代码如下:

protocol Request {

    var firstName: String? { get }
    var lastName: String? { get }

    var email: String? { get }
    var password: String? { get }
    var repeatedPassword: String? { get }
} 

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)
    }
}

所以我可以创建一个 PermissionHandler、LocationHandler、LoginHandler、一个 SignupHandler 并将它们组合成链。到目前为止一切顺利。

现在我想为其他目的创建一个责任链,比方说具有不同类型 MediaContentHandler 的 MediaContentPlayer CoR,我想使用泛型重构和重用基本代码。

所以我从Handler协议开始:

protocol Handler {

    associatedtype HandlerRequest
    var next: Handler? { get }

    func handle(_ request: HandlerRequest) -> LocalizedError?
}

但我收到错误“协议‘处理程序’只能用作通用约束,因为它具有自身或关联的类型要求”。

在使用关联类型时,有没有办法在协议本身内部引用协议?或者另一种方式使上述代码不依赖于特定类型?

【问题讨论】:

    标签: swift generics design-patterns protocols associated-types


    【解决方案1】:

    你会照顾这样的事情:

    protocol Handler {
        // ...
        var next: some Handler<HandlerRequest == Self.HandlerRequest>?  { get }
        // ...
    }
    

    这里的问题是 Swift (还)不支持不透明的返回类型,这些返回类型是具有关联类型的协议。

    解决此限制的方法是为 next 属性使用类型橡皮擦:

    protocol Handler {
        associatedtype HandlerRequest
    
        // shift the generic from a protocol with associated type to a generic struct
        var next: AnyHandler<HandlerRequest>? { get }
    
        func handle(_ request: HandlerRequest) -> LocalizedError?
    }
    
    struct AnyHandler<HandlerRequest> {
    
        private var _handle: (HandlerRequest) -> LocalizedError?
        private var _next: () -> AnyHandler<HandlerRequest>?
    
        init<H: Handler>(_ handler: H) where H.HandlerRequest == HandlerRequest {
            _next = { handler.next }
            _handle = handler.handle
        }
    }
    
    extension AnyHandler: Handler {
    
        var next: AnyHandler<HandlerRequest>? { return _next() }
    
        func handle(_ request: HandlerRequest) -> LocalizedError? {
            return _handle(request)
        }
    }
    

    这样您既可以受益于协议,又可以将next 属性绑定到您需要的处理程序请求类型。

    作为使用协议的额外好处,您仍然可以从基类中受益于默认实现:

    extension Handler {
        func handle(_ request: HandlerRequest) -> LocalizedError? {
            return next?.handle(request)
        }
    }
    

    这就是 Swift 中的协议有多酷,通过改进多态性的概念,它们允许您避免使用类并尽可能多地使用值类型。


    使用示例:

    struct LoginHandler: Handler {
        var next: AnyHandler<AccountRequest>?
    
        func handle(_ request: AccountRequest) -> LocalizedError? {
            // do the login validation
        }
    }
    
    struct SignupHandler: Handler {
        var next: AnyHandler<AccountRequest>?
    
        func handle(_ request: AccountRequest) -> LocalizedError? {
            // do the signup validation
        }
    }
    
    extension Handler {
        // Helper function to easily create a type erased AnyHandler instance    
        func erase() -> AnyHandler<HandlerRequest> {
            return AnyHandler(self)
        }
    }
    
    // now let's put the handers to work:
    let loginHandler = LoginHandler()
    let signupHandler = SignupHandler(next: loginHandler.erase())
    let someOtherAccountHandler = SomeOtherAccountHandler(next: signupHandler.erase())
    

    【讨论】:

    • 看起来很强大.. 但是我仍然不清楚我现在如何从这个基本代码创建两个专门的和不同的链。我想要一个带有处理 AccountRequest 类型的 LoginHandler、SignupHandler 等的链,以及一个带有处理 ContentRequests 类型的 AudioHandler、VideoHandler 等的不同链。每个处理程序都应该能够覆盖句柄函数以添加特定的行为条件。这可能吗?
    • @ChristianSlanzi 我更新了答案并在最后添加了一个使用示例。希望这能让事情更清楚。
    【解决方案2】:

    我会使用以下

    protocol Handler {
    
        associatedtype HandlerRequest
        associatedtype NextHandler: Handler
    
        var next: Self.NextHandler? { get }
    
        func handle(_ request: HandlerRequest) -> LocalizedError?
    }
    

    【讨论】:

    • 看起来很简单!不过还有一个问题。使用这个解决方案,我应该创建两个不同的 BaseHandler 类,比如 LoginModuleBaseHandler 用于 LoginRequest 类型,ContentPlayerBaseHandler 用于 ContentRequest 类型,还是我也可以保留一个通用 BaseHandler 类,然后为两种不同的基本类型派生两个不同的链?或者换句话说,如果我在 BaseHandler 类中定义了一个 typealias,我如何在派生 BaseHandler 的特定子类时覆盖它/添加约束?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-18
    相关资源
    最近更新 更多