Swift Mediator模式-通用协议的集合

时间:2019-01-03 12:14:28

标签: swift generics design-patterns mediator

我正在尝试在Swift中实现通用Mediator pattern并具有以下协议和类:

protocol Request {
}

protocol Handler {
    associatedtype TRequest = Request

    func handle(_ request: TRequest)
}

class RequestProcessor {

    func register<THandler: Handler>(_ handler: THandler) {

    }

    func handle(_ request: Request) {

    }

}

以预期用途为例(例如):

struct LoginRequest: Request {
    let username: String
    let password: String
}

struct LogoutRequest: Request {
    let userId: Int
}

class LoginHandler: Handler {
    func handle(_ request: LoginRequest) {
        // do something
    }
}

class LogoutHandler: Handler {
    func handle(_ request: LogoutRequest) {
        // do something
    }
}

// Setup the processor and register handlers

let processor = RequestProcessor()
processor.register(LoginHandler())
processor.register(LogoutHandler())

// The processor handles any kind of Request, in this case a LoginRequest

processor.handle(LoginRequest(username: "steve", password: "..."))

// The LoginHandler's handle method will be called

但是我不确定如何存储Handler对象的集合,因为它是具有关联类型的协议。我知道类型擦除,并在此处阅读了多个答案以及有关该主题的各种文章(12),但不确定如何将其应用于我的情况。

1 个答案:

答案 0 :(得分:2)

首先,标准建议:

  

我正在尝试在Swift中实现通用介体模式

不要。从您要解决的实际问题开始,并为该问题设计好的和必要的抽象。不要仅仅为了通用而创建通用的东西。斯威夫特会一遍又一遍地咬你。甚至真正需要超类事物的stdlib,也通常必须走出纯Swift才能实现(使用编译器特殊知识和gyb模板)。 “成为通用”本身并不是目标。您几乎可以肯定使它变得太复杂了。每个人都做。

好的,那是不对的。第二条建议:这不是对带有关联类型(PAT)的协议的很好的使用。 PAT的重点是向类型添加方法,而不是向 be 类型添加方法。您永远不会传递Collection本身或存储该“类型”的东西。您创建的方法通常可以在任何Collection类型上工作。没有[Collection]这样的类型。

您的方法的根本问题在于,没有办法实现RequestProcessor.process()而不依靠as?强制转换,这会破坏类型安全性。 processor如何知道如何呼叫LoginHandler.process?为什么是那个?如果两个不同的处理程序接受了LoginRequest,该怎么办?如果没有处理程序接受该类型怎么办?

您在这里设计的不是Mediator模式。 Mediator模式将共享一个接口的同事聚集在一起,因此看起来像这样:

class RequestProcessor<Request> {

    var handlers: [(Request) -> Void] = []
    func register(handler: @escaping (Request) -> Void) {
        handlers.append(handler)
    }

    func handle(request: Request) {
        for handler in handlers {
            handler(request)
        }
    }
}

对于每种请求,您将拥有一个RequestProcessor,而不是通用的“每种请求的处理器”。 (在Swift中)创建一个必需的泛型将消除类型安全性,在这种情况下,您基本上是在创建稍微更快速的NotificationCenter。 (可以创建此类型的类型安全的版本,但是它需要依赖类型,这是Swift所没有的非常复杂的类型功能。)

好,所以也许您真的想要这个中央集线器,谁需要类型安全?为什么不?您只需要说出您的意思,那就是任何处理程序都必须能够接受任何请求,即使该请求不起作用。编译器无法证明比这更具体的东西,因为在编译时它不知道类型。很好,as?到死。

protocol Request {}

protocol Handler {
    func canHandle(_ request: Request) -> Bool
    func handle(_ request: Request)
}

class RequestProcessor {

    private var handlers: [Handler] = []

    func register(_ handler: Handler) {
        handlers.append(handler)
    }

    func handle(_ request: Request) {
        for handler in handlers where handler.canHandle(request) {
            handler.handle(request)
        }
    }
}

class LoginHandler: Handler {
    func canHandle(_ request: Request) -> Bool {
        return request is LoginRequest
    }

    func handle(_ request: Request) {
        guard let loginRequest = request as? LoginRequest else { return }
        // handle loginRequest
    }
}

但是我几乎可以肯定会摆脱Mediator模式。如果目标是换入和换出处理器以进行测试或其他,我将仅使用典型的依赖注入技术。将LoginHandler传递给创建LoginRequest的任何方法。