首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >返回的接口吗?但混凝土还有些混凝土

返回的接口吗?但混凝土还有些混凝土
EN

Stack Overflow用户
提问于 2018-08-28 20:38:04
回答 2查看 117关注 0票数 2

我相信我已经解决了这个问题,这要归功于当前的1指针答案和泛型。我将更新下面的答案,并将其添加到其中。谢谢!

试图创建命令总线

我正努力在Swift中创建一个命令总线。我现在遇到的问题是,我试图使这个东西足够通用,以处理不同的命令,但是在许多情况下,我必须返回一个Any,这意味着我必须一直在执行代码检查,而且我不知道我能做些什么。

代码

代码语言:javascript
复制
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
    }
}

最后,下面是命令总线,它基本上只是将给定给它的命令映射到一个处理程序并返回它。没有什么特别的或复杂的。

代码语言:javascript
复制
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。我现在必须向函数传递一个带有type声明的命令:

代码语言:javascript
复制
let handler: ConcreteHandlerName = commandBus.retrieveHandler(command)
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-09-04 11:00:15

我不确定这是否是你想要的,因为我不太明白你想要实现什么,但是看看我已经整理好的游乐场代码:

代码语言:javascript
复制
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属性,而是添加了一个添加映射的方法。这样,在创建地图时,类型总是正确的:

代码语言:javascript
复制
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)
票数 1
EN

Stack Overflow用户

发布于 2018-08-29 19:59:20

associatedtype是你问题的答案:

代码语言:javascript
复制
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
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52065876

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档