我相信我已经通过当前的1pointer答案和泛型解决了这一问题。我将在下面更新答案并将其添加到该答案中。谢谢!
我正在努力在Swift中创建命令总线。我现在遇到的问题是,我试图使这种东西足够通用以处理不同的命令,但是结果在很多情况下,我必须返回一个Any
,这意味着我将不得不一直在对所有内容执行代码内检查,但我不确定该怎么做。
protocol Command { }
struct TestCommand: Command
{
public let value: int = 1
}
protocol CommandHandler
{
func execute(_ command: Command) -> Any?
}
struct TestCommandHandler: CommandHandler
{
public func execute(_ command: Command) -> Any?
{
// First problem - I have to cast command as! TestCommand
// due to the interface
}
}
最后是命令总线,它基本上只是将分配给它的命令映射到处理程序,然后将其返回。没什么特别的或复杂的。
struct CommandBus
{
public let container: Container
/// Added as closures so the commands are only resolved when requested
private func commandMap() -> Array<[String: () -> (Any)]>
{
// TestCommand implements an empty protocol CommandProtocol
return [
[String(describing: TestCommand.self): { self.container.resolve(TestCommandHandler.self)! }]
]
}
/// Dispatch a command to the relevant command handler
public func dispatch(_ command: Command) -> Any
{
let map = self.commandMap()
let commandName = String(describing: command.self)
let element = map.enumerated().first(where: { $0.element.keys.first == commandName })
let elementIndex = map.index(element!.offset, offsetBy: 0)
let commandHandler: CommandHandler = map[elementIndex].first!.value() as! CommandHandler
return commandHandler.execute(command)!
}
}
命令总线的唯一职责实际上是弄清楚要调用哪个命令处理程序并返回结果。
这是我现在所遇到的问题:
as
进行强制转换,以便使用它(在这种情况下,处理程序希望能够从命令中获取value
)Any
,因此无论我返回的标量如何,都必须检查它们是否存在于创建命令的控制器中并将其传递给命令总线那么-我可以在这里使用泛型来解决我的任何问题吗?这是更多的体系结构还是OO问题?还是因为这是一种严格类型的语言,我基本上无法做到吗?
我认为这里显然缺少一些东西。我如何创建我需要的东西,同时又保持体面的打字,而不必在每一个步骤的每一步都告诉编译器。这有可能吗?
有人建议我也可以将协议与关联类型一起使用,但是我不确定确切将其放置在何处或如何执行。我还想到了“请求/响应”样式的事物,其中每个命令都返回一个响应,但这必须是一个协议,基本上使我回到了Any
问题。
我还尝试将CommandBus
签名更改为:public func retrieveHandler<T: CommandHandler>(_ command: Command) -> T
。现在,我必须使用类型声明将命令传递给函数:
let handler: ConcreteHandlerName = commandBus.retrieveHandler(command)
答案 0 :(得分:1)
associatedtype
是您问题的答案:
protocol Command { }
protocol CommandHandler {
associatedtype CommandType: Command // the concrete `CommandType` must conform to `Command`
associatedtype ReturnType // each handler will define what the return type is
func execute(_ command: CommandType) -> ReturnType?
}
struct TestCommand: Command {
public let value = 1
}
struct TestCommandHandler: CommandHandler {
typealias CommandType = TestCommand
typealias ReturnType = Int
public func execute(_ command: CommandType) -> ReturnType? {
// now `command` is of type `TestCommand` and the return value is `Int?`
return 42
}
}
我不确定您的CommandBus
应该做什么,所以我跳过了您的问题的那一部分。
答案 1 :(得分:1)
不确定这是否是您想要的,因为我不太了解您要实现的目标,但是请看一下我整理的Playground代码:
import Foundation
protocol CommandProtocol {
associatedtype Handler: CommandHandlerProtocol
associatedtype Result
}
protocol CommandHandlerProtocol {
associatedtype Command: CommandProtocol
func execute(_ command: Command) -> Command.Result
init()
}
class IntCommand: CommandProtocol {
typealias Handler = IntCommandHandler
typealias Result = Int
}
class IntCommandHandler: CommandHandlerProtocol {
typealias Command = IntCommand
func execute(_ command: Command) -> Command.Result {
return 5
}
required init() { }
}
class StringCommand: CommandProtocol {
typealias Handler = StringCommandHandler
typealias Result = String
}
class StringCommandHandler: CommandHandlerProtocol {
typealias Command = StringCommand
func execute(_ command: Command) -> Command.Result {
return "Hello!"
}
required init() { }
}
class CommandBus {
public var map: [String: Any] = [:]
func dispatch<T: CommandProtocol>(_ command: T) -> T.Handler.Command.Result {
let handlerClass = map[String(describing: type(of: command))] as! T.Handler.Type
let handler = handlerClass.init() as T.Handler
return handler.execute(command as! T.Handler.Command)
}
}
let commandBus = CommandBus()
commandBus.map[String(describing: IntCommand.self)] = IntCommandHandler.self
commandBus.map[String(describing: StringCommand.self)] = StringCommandHandler.self
let intResult = commandBus.dispatch(IntCommand())
print(intResult)
let stringResult = commandBus.dispatch(StringCommand())
print(stringResult)
这是一个CommandBus
,它映射到处理程序的特定实例,而不仅仅是类型。这意味着可以删除init
中的CommandHandlerProtocol
方法。
我还隐藏了map
属性,而是添加了一种添加映射的方法。这样,在创建地图时您的类型始终正确:
protocol CommandHandlerProtocol {
associatedtype Command: CommandProtocol
func execute(_ command: Command) -> Command.Result
}
class CommandBus {
private var map: [String: Any] = [:]
func map<T: CommandProtocol>(_ commandType: T.Type, to handler: T.Handler) {
map[String(describing: commandType)] = handler
}
func dispatch<T: CommandProtocol>(_ command: T) -> T.Handler.Command.Result {
let handler = map[String(describing: type(of: command))] as! T.Handler
return handler.execute(command as! T.Handler.Command)
}
}
let commandBus = CommandBus()
commandBus.map(IntCommand.self, to: IntCommandHandler())
commandBus.map(StringCommand.self, to: StringCommandHandler())
let intResult = commandBus.dispatch(IntCommand())
print(intResult)
let stringResult = commandBus.dispatch(StringCommand())
print(stringResult)