我正在尝试在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
对象的集合,因为它是具有关联类型的协议。我知道类型擦除,并在此处阅读了多个答案以及有关该主题的各种文章(1,2),但不确定如何将其应用于我的情况。
答案 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
的任何方法。