接口返回任何?但混凝土能返回混凝土

时间:2018-08-28 20:38:05

标签: swift oop

  

我相信我已经通过当前的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)

2 个答案:

答案 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)

enter image description here

更新

这是一个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)